Skip to content

Commit b30b0e0

Browse files
matthewpbluwy
andauthored
Support prerender in Netlify redirects (#5904)
* Support prerender in Netlify redirects * Updated sorting algorithm * Update packages/integrations/netlify/src/shared.ts Co-authored-by: Bjorn Lu <bjornlu.dev@gmail.com> Co-authored-by: Bjorn Lu <bjornlu.dev@gmail.com>
1 parent 1d76982 commit b30b0e0

11 files changed

Lines changed: 235 additions & 17 deletions

File tree

packages/integrations/netlify/src/integration-edge-functions.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -163,7 +163,7 @@ export function netlifyEdgeFunctions({ dist }: NetlifyEdgeFunctionsOptions = {})
163163
'astro:build:done': async ({ routes, dir }) => {
164164
await bundleServerEntry(_buildConfig, _vite);
165165
await createEdgeManifest(routes, entryFile, _config.root);
166-
await createRedirects(routes, dir, entryFile, true);
166+
await createRedirects(_config, routes, dir, entryFile, true);
167167
},
168168
},
169169
};

packages/integrations/netlify/src/integration-functions.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,7 @@ function netlifyFunctions({
4848
}
4949
},
5050
'astro:build:done': async ({ routes, dir }) => {
51-
await createRedirects(routes, dir, entryFile, false);
51+
await createRedirects(_config, routes, dir, entryFile, false);
5252
},
5353
},
5454
};
Lines changed: 102 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,16 @@
1-
import type { RouteData } from 'astro';
1+
import type { AstroConfig, RouteData } from 'astro';
22
import fs from 'fs';
33

4+
type RedirectDefinition = {
5+
dynamic: boolean;
6+
input: string;
7+
target: string;
8+
weight: 0 | 1;
9+
status: 200 | 404;
10+
};
11+
412
export async function createRedirects(
13+
config: AstroConfig,
514
routes: RouteData[],
615
dir: URL,
716
entryFile: string,
@@ -10,37 +19,116 @@ export async function createRedirects(
1019
const _redirectsURL = new URL('./_redirects', dir);
1120
const kind = edge ? 'edge-functions' : 'functions';
1221

13-
// Create the redirects file that is used for routing.
14-
let _redirects = '';
22+
const definitions: RedirectDefinition[] = [];
23+
1524
for (const route of routes) {
1625
if (route.pathname) {
1726
if (route.distURL) {
18-
_redirects += `
19-
${route.pathname} /${route.distURL.toString().replace(dir.toString(), '')} 200`;
27+
definitions.push({
28+
dynamic: false,
29+
input: route.pathname,
30+
target: prependForwardSlash(route.distURL.toString().replace(dir.toString(), '')),
31+
status: 200,
32+
weight: 1
33+
});
2034
} else {
21-
_redirects += `
22-
${route.pathname} /.netlify/${kind}/${entryFile} 200`;
35+
definitions.push({
36+
dynamic: false,
37+
input: route.pathname,
38+
target: `/.netlify/${kind}/${entryFile}`,
39+
status: 200,
40+
weight: 1,
41+
});
2342

2443
if (route.route === '/404') {
25-
_redirects += `
26-
/* /.netlify/${kind}/${entryFile} 404`;
44+
definitions.push({
45+
dynamic: true,
46+
input: '/*',
47+
target: `/.netlify/${kind}/${entryFile}`,
48+
status: 404,
49+
weight: 0
50+
});
2751
}
2852
}
2953
} else {
3054
const pattern =
31-
'/' + route.segments.map(([part]) => (part.dynamic ? '*' : part.content)).join('/');
55+
'/' + route.segments.map(([part]) => {
56+
//(part.dynamic ? '*' : part.content)
57+
if(part.dynamic) {
58+
if(part.spread) {
59+
return '*';
60+
} else {
61+
return ':' + part.content;
62+
}
63+
} else {
64+
return part.content;
65+
}
66+
}).join('/');
67+
3268
if (route.distURL) {
33-
_redirects += `
34-
${pattern} /${route.distURL.toString().replace(dir.toString(), '')} 200`;
69+
const target = `${pattern}` + (config.build.format === 'directory' ? '/index.html' : '.html');
70+
definitions.push({
71+
dynamic: true,
72+
input: pattern,
73+
target,
74+
status: 200,
75+
weight: 1
76+
});
3577
} else {
36-
_redirects += `
37-
${pattern} /.netlify/${kind}/${entryFile} 200`;
78+
definitions.push({
79+
dynamic: true,
80+
input: pattern,
81+
target: `/.netlify/${kind}/${entryFile}`,
82+
status: 200,
83+
weight: 1
84+
});
3885
}
3986
}
4087
}
4188

89+
let _redirects = prettify(definitions);
90+
4291
// Always use appendFile() because the redirects file could already exist,
4392
// e.g. due to a `/public/_redirects` file that got copied to the output dir.
4493
// If the file does not exist yet, appendFile() automatically creates it.
4594
await fs.promises.appendFile(_redirectsURL, _redirects, 'utf-8');
4695
}
96+
97+
function prettify(definitions: RedirectDefinition[]) {
98+
let minInputLength = 0, minTargetLength = 0;
99+
definitions.sort((a, b) => {
100+
// Find the longest input, so we can format things nicely
101+
if(a.input.length > minInputLength) {
102+
minInputLength = a.input.length;
103+
}
104+
if(b.input.length > minInputLength) {
105+
minInputLength = b.input.length;
106+
}
107+
108+
// Same for the target
109+
if(a.target.length > minTargetLength) {
110+
minTargetLength = a.target.length;
111+
}
112+
if(b.target.length > minTargetLength) {
113+
minTargetLength = b.target.length;
114+
}
115+
116+
// Sort dynamic routes on top
117+
return b.weight - a.weight;
118+
});
119+
120+
let _redirects = '';
121+
// Loop over the definitions
122+
definitions.forEach((defn, i) => {
123+
// Figure out the number of spaces to add. We want at least 4 spaces
124+
// after the input. This ensure that all targets line up together.
125+
let inputSpaces = (minInputLength - defn.input.length) + 4;
126+
let targetSpaces = (minTargetLength - defn.target.length) + 4;
127+
_redirects += (i === 0 ? '' : '\n') + defn.input + ' '.repeat(inputSpaces) + defn.target + ' '.repeat(Math.abs(targetSpaces)) + defn.status;
128+
});
129+
return _redirects;
130+
}
131+
132+
function prependForwardSlash(str: string) {
133+
return str[0] === '/' ? str : '/' + str;
134+
}

packages/integrations/netlify/test/functions/dynamic-route.test.js

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,13 @@ describe('Dynamic pages', () => {
2121

2222
it('Dynamic pages are included in the redirects file', async () => {
2323
const redir = await fixture.readFile('/_redirects');
24-
expect(redir).to.match(/\/products\/\*/);
24+
expect(redir).to.match(/\/products\/:id/);
25+
});
26+
27+
it('Prerendered routes are also included using placeholder syntax', async () => {
28+
const redir = await fixture.readFile('/_redirects');
29+
expect(redir).to.include('/pets/:cat /pets/:cat/index.html 200');
30+
expect(redir).to.include('/pets/:dog /pets/:dog/index.html 200');
31+
expect(redir).to.include('/pets /.netlify/functions/entry 200');
2532
});
2633
});
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
---
2+
export const prerender = true
3+
4+
export function getStaticPaths() {
5+
return [
6+
{
7+
params: {cat: 'cat1'},
8+
props: {cat: 'cat1'}
9+
},
10+
{
11+
params: {cat: 'cat2'},
12+
props: {cat: 'cat2'}
13+
},
14+
{
15+
params: {cat: 'cat3'},
16+
props: {cat: 'cat3'}
17+
},
18+
];
19+
}
20+
21+
const { cat } = Astro.props;
22+
23+
---
24+
25+
<div>Good cat, {cat}!</div>
26+
27+
<a href="/">back</a>
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
---
2+
export const prerender = true
3+
4+
export function getStaticPaths() {
5+
return [
6+
{
7+
params: {dog: 'dog1'},
8+
props: {dog: 'dog1'}
9+
},
10+
{
11+
params: {dog: 'dog2'},
12+
props: {dog: 'dog2'}
13+
},
14+
{
15+
params: {dog: 'dog3'},
16+
props: {dog: 'dog3'}
17+
},
18+
];
19+
}
20+
21+
const { dog } = Astro.props;
22+
23+
---
24+
25+
<div>Good dog, {dog}!</div>
26+
27+
<a href="/">back</a>
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
<html lang="en">
2+
<head>
3+
<meta charset="utf-8" />
4+
<link rel="icon" type="image/svg+xml" href="/favicon.svg" />
5+
<meta name="viewport" content="width=device-width" />
6+
<meta name="generator" content={Astro.generator} />
7+
<title>Astro</title>
8+
</head>
9+
<body>
10+
<h1>Astro</h1>
11+
</body>
12+
</html>
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
<html>
2+
<head>
3+
<title>Testing</title>
4+
</head>
5+
<body>
6+
<h1>testing</h1>
7+
</body>
8+
</html>
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
<html>
2+
<head>
3+
<title>Testing</title>
4+
</head>
5+
<body>
6+
<h1>testing</h1>
7+
</body>
8+
</html>
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
---
2+
export const prerender = true;
3+
---
4+
<html>
5+
<head>
6+
<title>Testing</title>
7+
</head>
8+
<body>
9+
<h1>testing</h1>
10+
</body>
11+
</html>

0 commit comments

Comments
 (0)