summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGravatar Matthew Phillips <matthew@skypack.dev> 2023-01-23 09:47:33 -0500
committerGravatar GitHub <noreply@github.com> 2023-01-23 09:47:33 -0500
commitf5adbd6b55ca13a7523dff2cfc5dccdab9980fa7 (patch)
treeb7955f7140c8ea757687441a5ac307e4db4b884d
parent9e57268f1318853b612711b31d7461e9b9ce1978 (diff)
downloadastro-f5adbd6b55ca13a7523dff2cfc5dccdab9980fa7.tar.gz
astro-f5adbd6b55ca13a7523dff2cfc5dccdab9980fa7.tar.zst
astro-f5adbd6b55ca13a7523dff2cfc5dccdab9980fa7.zip
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>
-rw-r--r--.changeset/real-rules-occur.md5
-rw-r--r--packages/integrations/netlify/src/integration-edge-functions.ts2
-rw-r--r--packages/integrations/netlify/src/integration-functions.ts2
-rw-r--r--packages/integrations/netlify/src/shared.ts116
-rw-r--r--packages/integrations/netlify/test/functions/dynamic-route.test.js9
-rw-r--r--packages/integrations/netlify/test/functions/fixtures/dynamic-route/src/pages/pets/[cat].astro27
-rw-r--r--packages/integrations/netlify/test/functions/fixtures/dynamic-route/src/pages/pets/[dog].astro27
-rw-r--r--packages/integrations/netlify/test/functions/fixtures/dynamic-route/src/pages/pets/index.astro12
-rw-r--r--packages/integrations/netlify/test/functions/fixtures/prerender/src/pages/404.astro8
-rw-r--r--packages/integrations/netlify/test/functions/fixtures/prerender/src/pages/index.astro8
-rw-r--r--packages/integrations/netlify/test/functions/fixtures/prerender/src/pages/one.astro11
-rw-r--r--packages/integrations/netlify/test/functions/prerender.test.js30
12 files changed, 240 insertions, 17 deletions
diff --git a/.changeset/real-rules-occur.md b/.changeset/real-rules-occur.md
new file mode 100644
index 000000000..10133a059
--- /dev/null
+++ b/.changeset/real-rules-occur.md
@@ -0,0 +1,5 @@
+---
+'@astrojs/netlify': patch
+---
+
+Support prerender in \_redirects
diff --git a/packages/integrations/netlify/src/integration-edge-functions.ts b/packages/integrations/netlify/src/integration-edge-functions.ts
index d443e1c0b..0d036be93 100644
--- a/packages/integrations/netlify/src/integration-edge-functions.ts
+++ b/packages/integrations/netlify/src/integration-edge-functions.ts
@@ -163,7 +163,7 @@ export function netlifyEdgeFunctions({ dist }: NetlifyEdgeFunctionsOptions = {})
'astro:build:done': async ({ routes, dir }) => {
await bundleServerEntry(_buildConfig, _vite);
await createEdgeManifest(routes, entryFile, _config.root);
- await createRedirects(routes, dir, entryFile, true);
+ await createRedirects(_config, routes, dir, entryFile, true);
},
},
};
diff --git a/packages/integrations/netlify/src/integration-functions.ts b/packages/integrations/netlify/src/integration-functions.ts
index e8ff4bd1f..f75b6d1f8 100644
--- a/packages/integrations/netlify/src/integration-functions.ts
+++ b/packages/integrations/netlify/src/integration-functions.ts
@@ -48,7 +48,7 @@ function netlifyFunctions({
}
},
'astro:build:done': async ({ routes, dir }) => {
- await createRedirects(routes, dir, entryFile, false);
+ await createRedirects(_config, routes, dir, entryFile, false);
},
},
};
diff --git a/packages/integrations/netlify/src/shared.ts b/packages/integrations/netlify/src/shared.ts
index 2c648984a..c87d946f5 100644
--- a/packages/integrations/netlify/src/shared.ts
+++ b/packages/integrations/netlify/src/shared.ts
@@ -1,7 +1,16 @@
-import type { RouteData } from 'astro';
+import type { AstroConfig, RouteData } from 'astro';
import fs from 'fs';
+type RedirectDefinition = {
+ dynamic: boolean;
+ input: string;
+ target: string;
+ weight: 0 | 1;
+ status: 200 | 404;
+};
+
export async function createRedirects(
+ config: AstroConfig,
routes: RouteData[],
dir: URL,
entryFile: string,
@@ -10,37 +19,116 @@ export async function createRedirects(
const _redirectsURL = new URL('./_redirects', dir);
const kind = edge ? 'edge-functions' : 'functions';
- // Create the redirects file that is used for routing.
- let _redirects = '';
+ const definitions: RedirectDefinition[] = [];
+
for (const route of routes) {
if (route.pathname) {
if (route.distURL) {
- _redirects += `
- ${route.pathname} /${route.distURL.toString().replace(dir.toString(), '')} 200`;
+ definitions.push({
+ dynamic: false,
+ input: route.pathname,
+ target: prependForwardSlash(route.distURL.toString().replace(dir.toString(), '')),
+ status: 200,
+ weight: 1
+ });
} else {
- _redirects += `
- ${route.pathname} /.netlify/${kind}/${entryFile} 200`;
+ definitions.push({
+ dynamic: false,
+ input: route.pathname,
+ target: `/.netlify/${kind}/${entryFile}`,
+ status: 200,
+ weight: 1,
+ });
if (route.route === '/404') {
- _redirects += `
- /* /.netlify/${kind}/${entryFile} 404`;
+ definitions.push({
+ dynamic: true,
+ input: '/*',
+ target: `/.netlify/${kind}/${entryFile}`,
+ status: 404,
+ weight: 0
+ });
}
}
} else {
const pattern =
- '/' + route.segments.map(([part]) => (part.dynamic ? '*' : part.content)).join('/');
+ '/' + route.segments.map(([part]) => {
+ //(part.dynamic ? '*' : part.content)
+ if(part.dynamic) {
+ if(part.spread) {
+ return '*';
+ } else {
+ return ':' + part.content;
+ }
+ } else {
+ return part.content;
+ }
+ }).join('/');
+
if (route.distURL) {
- _redirects += `
- ${pattern} /${route.distURL.toString().replace(dir.toString(), '')} 200`;
+ const target = `${pattern}` + (config.build.format === 'directory' ? '/index.html' : '.html');
+ definitions.push({
+ dynamic: true,
+ input: pattern,
+ target,
+ status: 200,
+ weight: 1
+ });
} else {
- _redirects += `
- ${pattern} /.netlify/${kind}/${entryFile} 200`;
+ definitions.push({
+ dynamic: true,
+ input: pattern,
+ target: `/.netlify/${kind}/${entryFile}`,
+ status: 200,
+ weight: 1
+ });
}
}
}
+ let _redirects = prettify(definitions);
+
// Always use appendFile() because the redirects file could already exist,
// e.g. due to a `/public/_redirects` file that got copied to the output dir.
// If the file does not exist yet, appendFile() automatically creates it.
await fs.promises.appendFile(_redirectsURL, _redirects, 'utf-8');
}
+
+function prettify(definitions: RedirectDefinition[]) {
+ let minInputLength = 0, minTargetLength = 0;
+ definitions.sort((a, b) => {
+ // Find the longest input, so we can format things nicely
+ if(a.input.length > minInputLength) {
+ minInputLength = a.input.length;
+ }
+ if(b.input.length > minInputLength) {
+ minInputLength = b.input.length;
+ }
+
+ // Same for the target
+ if(a.target.length > minTargetLength) {
+ minTargetLength = a.target.length;
+ }
+ if(b.target.length > minTargetLength) {
+ minTargetLength = b.target.length;
+ }
+
+ // Sort dynamic routes on top
+ return b.weight - a.weight;
+ });
+
+ let _redirects = '';
+ // Loop over the definitions
+ definitions.forEach((defn, i) => {
+ // Figure out the number of spaces to add. We want at least 4 spaces
+ // after the input. This ensure that all targets line up together.
+ let inputSpaces = (minInputLength - defn.input.length) + 4;
+ let targetSpaces = (minTargetLength - defn.target.length) + 4;
+ _redirects += (i === 0 ? '' : '\n') + defn.input + ' '.repeat(inputSpaces) + defn.target + ' '.repeat(Math.abs(targetSpaces)) + defn.status;
+ });
+ return _redirects;
+}
+
+function prependForwardSlash(str: string) {
+ return str[0] === '/' ? str : '/' + str;
+}
diff --git a/packages/integrations/netlify/test/functions/dynamic-route.test.js b/packages/integrations/netlify/test/functions/dynamic-route.test.js
index 0cfb5359b..6bb68eab8 100644
--- a/packages/integrations/netlify/test/functions/dynamic-route.test.js
+++ b/packages/integrations/netlify/test/functions/dynamic-route.test.js
@@ -21,6 +21,13 @@ describe('Dynamic pages', () => {
it('Dynamic pages are included in the redirects file', async () => {
const redir = await fixture.readFile('/_redirects');
- expect(redir).to.match(/\/products\/\*/);
+ expect(redir).to.match(/\/products\/:id/);
+ });
+
+ it('Prerendered routes are also included using placeholder syntax', async () => {
+ const redir = await fixture.readFile('/_redirects');
+ expect(redir).to.include('/pets/:cat /pets/:cat/index.html 200');
+ expect(redir).to.include('/pets/:dog /pets/:dog/index.html 200');
+ expect(redir).to.include('/pets /.netlify/functions/entry 200');
});
});
diff --git a/packages/integrations/netlify/test/functions/fixtures/dynamic-route/src/pages/pets/[cat].astro b/packages/integrations/netlify/test/functions/fixtures/dynamic-route/src/pages/pets/[cat].astro
new file mode 100644
index 000000000..f86ee6ca9
--- /dev/null
+++ b/packages/integrations/netlify/test/functions/fixtures/dynamic-route/src/pages/pets/[cat].astro
@@ -0,0 +1,27 @@
+---
+export const prerender = true
+
+export function getStaticPaths() {
+ return [
+ {
+ params: {cat: 'cat1'},
+ props: {cat: 'cat1'}
+ },
+ {
+ params: {cat: 'cat2'},
+ props: {cat: 'cat2'}
+ },
+ {
+ params: {cat: 'cat3'},
+ props: {cat: 'cat3'}
+ },
+ ];
+}
+
+const { cat } = Astro.props;
+
+---
+
+<div>Good cat, {cat}!</div>
+
+<a href="/">back</a>
diff --git a/packages/integrations/netlify/test/functions/fixtures/dynamic-route/src/pages/pets/[dog].astro b/packages/integrations/netlify/test/functions/fixtures/dynamic-route/src/pages/pets/[dog].astro
new file mode 100644
index 000000000..0f3300f04
--- /dev/null
+++ b/packages/integrations/netlify/test/functions/fixtures/dynamic-route/src/pages/pets/[dog].astro
@@ -0,0 +1,27 @@
+---
+export const prerender = true
+
+export function getStaticPaths() {
+ return [
+ {
+ params: {dog: 'dog1'},
+ props: {dog: 'dog1'}
+ },
+ {
+ params: {dog: 'dog2'},
+ props: {dog: 'dog2'}
+ },
+ {
+ params: {dog: 'dog3'},
+ props: {dog: 'dog3'}
+ },
+ ];
+}
+
+const { dog } = Astro.props;
+
+---
+
+<div>Good dog, {dog}!</div>
+
+<a href="/">back</a>
diff --git a/packages/integrations/netlify/test/functions/fixtures/dynamic-route/src/pages/pets/index.astro b/packages/integrations/netlify/test/functions/fixtures/dynamic-route/src/pages/pets/index.astro
new file mode 100644
index 000000000..d1423f8ef
--- /dev/null
+++ b/packages/integrations/netlify/test/functions/fixtures/dynamic-route/src/pages/pets/index.astro
@@ -0,0 +1,12 @@
+<html lang="en">
+ <head>
+ <meta charset="utf-8" />
+ <link rel="icon" type="image/svg+xml" href="/favicon.svg" />
+ <meta name="viewport" content="width=device-width" />
+ <meta name="generator" content={Astro.generator} />
+ <title>Astro</title>
+ </head>
+ <body>
+ <h1>Astro</h1>
+ </body>
+</html>
diff --git a/packages/integrations/netlify/test/functions/fixtures/prerender/src/pages/404.astro b/packages/integrations/netlify/test/functions/fixtures/prerender/src/pages/404.astro
new file mode 100644
index 000000000..ad5d44aa2
--- /dev/null
+++ b/packages/integrations/netlify/test/functions/fixtures/prerender/src/pages/404.astro
@@ -0,0 +1,8 @@
+<html>
+<head>
+ <title>Testing</title>
+</head>
+<body>
+ <h1>testing</h1>
+</body>
+</html>
diff --git a/packages/integrations/netlify/test/functions/fixtures/prerender/src/pages/index.astro b/packages/integrations/netlify/test/functions/fixtures/prerender/src/pages/index.astro
new file mode 100644
index 000000000..ad5d44aa2
--- /dev/null
+++ b/packages/integrations/netlify/test/functions/fixtures/prerender/src/pages/index.astro
@@ -0,0 +1,8 @@
+<html>
+<head>
+ <title>Testing</title>
+</head>
+<body>
+ <h1>testing</h1>
+</body>
+</html>
diff --git a/packages/integrations/netlify/test/functions/fixtures/prerender/src/pages/one.astro b/packages/integrations/netlify/test/functions/fixtures/prerender/src/pages/one.astro
new file mode 100644
index 000000000..12146450e
--- /dev/null
+++ b/packages/integrations/netlify/test/functions/fixtures/prerender/src/pages/one.astro
@@ -0,0 +1,11 @@
+---
+export const prerender = true;
+---
+<html>
+<head>
+ <title>Testing</title>
+</head>
+<body>
+ <h1>testing</h1>
+</body>
+</html>
diff --git a/packages/integrations/netlify/test/functions/prerender.test.js b/packages/integrations/netlify/test/functions/prerender.test.js
new file mode 100644
index 000000000..324ebc5c5
--- /dev/null
+++ b/packages/integrations/netlify/test/functions/prerender.test.js
@@ -0,0 +1,30 @@
+import { expect } from 'chai';
+import netlifyAdapter from '../../dist/index.js';
+import { loadFixture, testIntegration } from './test-utils.js';
+
+describe('Mixed Prerendering with SSR', () => {
+ /** @type {import('./test-utils').Fixture} */
+ let fixture;
+
+ before(async () => {
+ fixture = await loadFixture({
+ root: new URL('./fixtures/prerender/', import.meta.url).toString(),
+ output: 'server',
+ adapter: netlifyAdapter({
+ dist: new URL('./fixtures/prerender/dist/', import.meta.url),
+ }),
+ site: `http://example.com`,
+ integrations: [testIntegration()],
+ });
+ await fixture.build();
+ });
+ it('Wildcard 404 is sorted last', async () => {
+ const redir = await fixture.readFile('/_redirects');
+ const baseRouteIndex = redir.indexOf('/ /.netlify/functions/entry 200');
+ const oneRouteIndex = redir.indexOf('/one /one/index.html 200');
+ const fourOhFourWildCardIndex = redir.indexOf('/* /.netlify/functions/entry 404');
+
+ expect(fourOhFourWildCardIndex).to.be.greaterThan(baseRouteIndex);
+ expect(fourOhFourWildCardIndex).to.be.greaterThan(oneRouteIndex);
+ });
+});