mirror of
https://github.com/withastro/astro.git
synced 2024-12-16 21:46:22 -05:00
Handle getStaticPaths
with prerendered pages (#5734)
* fix(#5661): ensure getStaticPaths is correctly handled for prerendered pages * test: add prerender getStaticPaths cases * chore: add changeset * test: add props to test suite * chore: update lockfile Co-authored-by: Nate Moore <nate@astro.build>
This commit is contained in:
parent
cc4606d65e
commit
55cea0a9d8
18 changed files with 408 additions and 5 deletions
5
.changeset/lemon-eagles-worry.md
Normal file
5
.changeset/lemon-eagles-worry.md
Normal file
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
'astro': patch
|
||||
---
|
||||
|
||||
Fix `prerender` when used with `getStaticPaths`
|
|
@ -1004,6 +1004,7 @@ export type AsyncRendererComponentFn<U> = (
|
|||
export interface ComponentInstance {
|
||||
default: AstroComponentFactory;
|
||||
css?: string[];
|
||||
prerender?: boolean;
|
||||
getStaticPaths?: (options: GetStaticPathsOptions) => GetStaticPathsResult;
|
||||
}
|
||||
|
||||
|
|
|
@ -181,7 +181,7 @@ async function getPathsForRoute(
|
|||
route: pageData.route,
|
||||
isValidate: false,
|
||||
logging: opts.logging,
|
||||
ssr: false,
|
||||
ssr: opts.settings.config.output === 'server',
|
||||
})
|
||||
.then((_result) => {
|
||||
const label = _result.staticPaths.length === 1 ? 'page' : 'pages';
|
||||
|
|
|
@ -139,6 +139,8 @@ function buildManifest(
|
|||
const joinBase = (pth: string) => (bareBase ? bareBase + '/' + pth : pth);
|
||||
|
||||
for (const pageData of eachPrerenderedPageData(internals)) {
|
||||
if (!pageData.route.pathname) continue;
|
||||
|
||||
const outFolder = getOutFolder(
|
||||
opts.settings.config,
|
||||
pageData.route.pathname!,
|
||||
|
|
|
@ -353,7 +353,7 @@ but ${plural ? 'none were.' : 'it was not.'} able to server-side render \`${comp
|
|||
'`getStaticPaths()` function is required for dynamic routes. Make sure that you `export` a `getStaticPaths` function from your dynamic route.',
|
||||
hint: `See https://docs.astro.build/en/core-concepts/routing/#dynamic-routes for more information on dynamic routes.
|
||||
|
||||
Alternatively, set \`output: "server"\` in your Astro config file to switch to a non-static server build.
|
||||
Alternatively, set \`output: "server"\` in your Astro config file to switch to a non-static server build. This error can also occur if using \`export const prerender = true;\`.
|
||||
See https://docs.astro.build/en/guides/server-side-rendering/ for more information on non-static rendering.`,
|
||||
},
|
||||
/**
|
||||
|
|
|
@ -31,7 +31,7 @@ export async function callGetStaticPaths({
|
|||
}: CallGetStaticPathsOptions): Promise<RouteCacheEntry> {
|
||||
validateDynamicRouteModule(mod, { ssr, logging, route });
|
||||
// No static paths in SSR mode. Return an empty RouteCacheEntry.
|
||||
if (ssr) {
|
||||
if (ssr && !mod.prerender) {
|
||||
return { staticPaths: Object.assign([], { keyed: new Map() }) };
|
||||
}
|
||||
// Add a check here to make TypeScript happy.
|
||||
|
|
|
@ -31,10 +31,10 @@ export function validateDynamicRouteModule(
|
|||
route: RouteData;
|
||||
}
|
||||
) {
|
||||
if (ssr && mod.getStaticPaths) {
|
||||
if (ssr && mod.getStaticPaths && !mod.prerender) {
|
||||
warn(logging, 'getStaticPaths', 'getStaticPaths() is ignored when "output: server" is set.');
|
||||
}
|
||||
if (!ssr && !mod.getStaticPaths) {
|
||||
if ((!ssr || mod.prerender) && !mod.getStaticPaths) {
|
||||
throw new AstroError({
|
||||
...AstroErrorData.GetStaticPathsRequired,
|
||||
location: { file: route.component },
|
||||
|
|
8
packages/astro/test/fixtures/ssr-prerender-get-static-paths/package.json
vendored
Normal file
8
packages/astro/test/fixtures/ssr-prerender-get-static-paths/package.json
vendored
Normal file
|
@ -0,0 +1,8 @@
|
|||
{
|
||||
"name": "@test/ssr-prerender-get-static-paths",
|
||||
"version": "0.0.0",
|
||||
"private": true,
|
||||
"dependencies": {
|
||||
"astro": "workspace:*"
|
||||
}
|
||||
}
|
22
packages/astro/test/fixtures/ssr-prerender-get-static-paths/src/pages/[...calledTwiceTest].astro
vendored
Normal file
22
packages/astro/test/fixtures/ssr-prerender-get-static-paths/src/pages/[...calledTwiceTest].astro
vendored
Normal file
|
@ -0,0 +1,22 @@
|
|||
---
|
||||
export function getStaticPaths({ paginate }) {
|
||||
if (globalThis.isCalledOnce) {
|
||||
throw new Error("Can only be called once!");
|
||||
}
|
||||
globalThis.isCalledOnce = true;
|
||||
return [
|
||||
{params: {calledTwiceTest: 'a'}},
|
||||
{params: {calledTwiceTest: 'b'}},
|
||||
{params: {calledTwiceTest: 'c'}},
|
||||
];
|
||||
}
|
||||
export const prerender = true;
|
||||
const { params } = Astro;
|
||||
---
|
||||
|
||||
<html>
|
||||
<head>
|
||||
<title>Page {params.calledTwiceTest}</title>
|
||||
</head>
|
||||
<body></body>
|
||||
</html>
|
19
packages/astro/test/fixtures/ssr-prerender-get-static-paths/src/pages/blog/[year]/[slug].astro
vendored
Normal file
19
packages/astro/test/fixtures/ssr-prerender-get-static-paths/src/pages/blog/[year]/[slug].astro
vendored
Normal file
|
@ -0,0 +1,19 @@
|
|||
---
|
||||
export async function getStaticPaths() {
|
||||
return [
|
||||
{ params: { year: '2022', slug: 'post-1' } },
|
||||
{ params: { year: 2022, slug: 'post-2' } },
|
||||
{ params: { slug: 'post-2', year: '2022' } },
|
||||
]
|
||||
}
|
||||
|
||||
export const prerender = true;
|
||||
const { year, slug } = Astro.params
|
||||
---
|
||||
|
||||
<html>
|
||||
<head>
|
||||
<title>{year} | {slug}</title>
|
||||
</head>
|
||||
<body></body>
|
||||
</html>
|
16
packages/astro/test/fixtures/ssr-prerender-get-static-paths/src/pages/data/[slug].json.ts
vendored
Normal file
16
packages/astro/test/fixtures/ssr-prerender-get-static-paths/src/pages/data/[slug].json.ts
vendored
Normal file
|
@ -0,0 +1,16 @@
|
|||
export const prerender = true;
|
||||
|
||||
export async function getStaticPaths() {
|
||||
return [
|
||||
{ params: { slug: 'thing1' } },
|
||||
{ params: { slug: 'thing2' } }
|
||||
];
|
||||
}
|
||||
|
||||
export async function get() {
|
||||
return {
|
||||
body: JSON.stringify({
|
||||
title: '[slug]'
|
||||
}, null, 4)
|
||||
};
|
||||
}
|
34
packages/astro/test/fixtures/ssr-prerender-get-static-paths/src/pages/food/[name].astro
vendored
Normal file
34
packages/astro/test/fixtures/ssr-prerender-get-static-paths/src/pages/food/[name].astro
vendored
Normal file
|
@ -0,0 +1,34 @@
|
|||
---
|
||||
export async function getStaticPaths() {
|
||||
return [
|
||||
{
|
||||
params: { name: 'tacos' },
|
||||
props: { yum: 10 },
|
||||
},
|
||||
{
|
||||
params: { name: 'potatoes' },
|
||||
props: { yum: 7 },
|
||||
},
|
||||
{
|
||||
params: { name: 'spaghetti' },
|
||||
props: { yum: 5 },
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
export const prerender = true;
|
||||
|
||||
const { yum } = Astro.props;
|
||||
---
|
||||
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width" />
|
||||
<title>Food</title>
|
||||
</head>
|
||||
<body>
|
||||
<p id="url">{ Astro.url.pathname }</p>
|
||||
<p id="props">{ yum }</p>
|
||||
</body>
|
||||
</html>
|
10
packages/astro/test/fixtures/ssr-prerender-get-static-paths/src/pages/nested-arrays/[slug].astro
vendored
Normal file
10
packages/astro/test/fixtures/ssr-prerender-get-static-paths/src/pages/nested-arrays/[slug].astro
vendored
Normal file
|
@ -0,0 +1,10 @@
|
|||
---
|
||||
export function getStaticPaths() {
|
||||
return [
|
||||
[ { params: {slug: "slug1"} } ],
|
||||
[ { params: {slug: "slug2"} } ],
|
||||
]
|
||||
}
|
||||
|
||||
export const prerender = true;
|
||||
---
|
23
packages/astro/test/fixtures/ssr-prerender-get-static-paths/src/pages/pizza/[...pizza].astro
vendored
Normal file
23
packages/astro/test/fixtures/ssr-prerender-get-static-paths/src/pages/pizza/[...pizza].astro
vendored
Normal file
|
@ -0,0 +1,23 @@
|
|||
---
|
||||
export function getStaticPaths() {
|
||||
return [{
|
||||
params: { pizza: 'papa-johns' },
|
||||
}, {
|
||||
params: { pizza: 'dominos' },
|
||||
}, {
|
||||
params: { pizza: 'grimaldis/new-york' },
|
||||
}]
|
||||
}
|
||||
export const prerender = true;
|
||||
const { pizza } = Astro.params
|
||||
---
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width" />
|
||||
<title>{pizza ?? 'The landing page'}</title>
|
||||
</head>
|
||||
<body>
|
||||
<h1>Welcome to {pizza ?? 'The landing page'}</h1>
|
||||
</body>
|
||||
</html>
|
|
@ -0,0 +1,22 @@
|
|||
---
|
||||
export function getStaticPaths() {
|
||||
return [{
|
||||
params: { cheese: 'mozzarella', topping: 'pepperoni' },
|
||||
}, {
|
||||
params: { cheese: 'provolone', topping: 'sausage' },
|
||||
}]
|
||||
}
|
||||
export const prerender = true;
|
||||
const { cheese, topping } = Astro.params
|
||||
---
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width" />
|
||||
<title>{cheese}</title>
|
||||
</head>
|
||||
<body>
|
||||
<h1>🍕 It's pizza time</h1>
|
||||
<p>{cheese}-{topping}</p>
|
||||
</body>
|
||||
</html>
|
30
packages/astro/test/fixtures/ssr-prerender-get-static-paths/src/pages/posts/[page].astro
vendored
Normal file
30
packages/astro/test/fixtures/ssr-prerender-get-static-paths/src/pages/posts/[page].astro
vendored
Normal file
|
@ -0,0 +1,30 @@
|
|||
---
|
||||
export async function getStaticPaths() {
|
||||
return [
|
||||
{
|
||||
params: { page: 1 },
|
||||
},
|
||||
{
|
||||
params: { page: 2 },
|
||||
},
|
||||
{
|
||||
params: { page: 3 }
|
||||
}
|
||||
]
|
||||
};
|
||||
export const prerender = true;
|
||||
const { page } = Astro.params
|
||||
const canonicalURL = new URL(Astro.url.pathname, Astro.site);
|
||||
---
|
||||
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width" />
|
||||
<title>Posts Page {page}</title>
|
||||
<link rel="canonical" href={canonicalURL.href}>
|
||||
</head>
|
||||
<body>
|
||||
<h1>Welcome to page {page}</h1>
|
||||
</body>
|
||||
</html>
|
202
packages/astro/test/ssr-prerender-get-static-paths.test.js
Normal file
202
packages/astro/test/ssr-prerender-get-static-paths.test.js
Normal file
|
@ -0,0 +1,202 @@
|
|||
import { expect } from 'chai';
|
||||
import { loadFixture } from './test-utils.js';
|
||||
import * as cheerio from 'cheerio';
|
||||
|
||||
describe('prerender getStaticPaths - build calls', () => {
|
||||
before(async () => {
|
||||
// reset the flag used by [...calledTwiceTest].astro between each test
|
||||
globalThis.isCalledOnce = false;
|
||||
|
||||
const fixture = await loadFixture({
|
||||
root: './fixtures/ssr-prerender-get-static-paths/',
|
||||
site: 'https://mysite.dev/',
|
||||
base: '/blog',
|
||||
});
|
||||
await fixture.build();
|
||||
});
|
||||
|
||||
it('is only called once during build', () => {
|
||||
// useless expect; if build() throws in setup then this test fails
|
||||
expect(true).to.equal(true);
|
||||
});
|
||||
});
|
||||
|
||||
describe('prerender getStaticPaths - dev calls', () => {
|
||||
let fixture;
|
||||
let devServer;
|
||||
|
||||
before(async () => {
|
||||
// reset the flag used by [...calledTwiceTest].astro between each test
|
||||
globalThis.isCalledOnce = false;
|
||||
|
||||
fixture = await loadFixture({ root: './fixtures/ssr-prerender-get-static-paths/' });
|
||||
devServer = await fixture.startDevServer();
|
||||
});
|
||||
|
||||
after(async () => {
|
||||
devServer.stop();
|
||||
});
|
||||
|
||||
it('only calls prerender getStaticPaths once', async () => {
|
||||
let res = await fixture.fetch('/a');
|
||||
expect(res.status).to.equal(200);
|
||||
|
||||
res = await fixture.fetch('/b');
|
||||
expect(res.status).to.equal(200);
|
||||
|
||||
res = await fixture.fetch('/c');
|
||||
expect(res.status).to.equal(200);
|
||||
});
|
||||
});
|
||||
|
||||
describe('prerender getStaticPaths - 404 behavior', () => {
|
||||
let fixture;
|
||||
let devServer;
|
||||
|
||||
before(async () => {
|
||||
// reset the flag used by [...calledTwiceTest].astro between each test
|
||||
globalThis.isCalledOnce = false;
|
||||
|
||||
fixture = await loadFixture({ root: './fixtures/ssr-prerender-get-static-paths/' });
|
||||
devServer = await fixture.startDevServer();
|
||||
});
|
||||
|
||||
after(async () => {
|
||||
devServer.stop();
|
||||
});
|
||||
|
||||
it('resolves 200 on matching static path - named params', async () => {
|
||||
const res = await fixture.fetch('/pizza/provolone-sausage');
|
||||
expect(res.status).to.equal(200);
|
||||
});
|
||||
|
||||
it('resolves 404 on pattern match without static path - named params', async () => {
|
||||
const res = await fixture.fetch('/pizza/provolone-pineapple');
|
||||
expect(res.status).to.equal(404);
|
||||
});
|
||||
|
||||
it('resolves 200 on matching static path - rest params', async () => {
|
||||
const res = await fixture.fetch('/pizza/grimaldis/new-york');
|
||||
expect(res.status).to.equal(200);
|
||||
});
|
||||
|
||||
it('resolves 404 on pattern match without static path - rest params', async () => {
|
||||
const res = await fixture.fetch('/pizza/pizza-hut');
|
||||
expect(res.status).to.equal(404);
|
||||
});
|
||||
});
|
||||
|
||||
describe('prerender getStaticPaths - route params type validation', () => {
|
||||
let fixture, devServer;
|
||||
|
||||
before(async () => {
|
||||
// reset the flag used by [...calledTwiceTest].astro between each test
|
||||
globalThis.isCalledOnce = false;
|
||||
|
||||
fixture = await loadFixture({ root: './fixtures/ssr-prerender-get-static-paths/' });
|
||||
devServer = await fixture.startDevServer();
|
||||
});
|
||||
|
||||
after(async () => {
|
||||
await devServer.stop();
|
||||
});
|
||||
|
||||
it('resolves 200 on nested array parameters', async () => {
|
||||
const res = await fixture.fetch('/nested-arrays/slug1');
|
||||
expect(res.status).to.equal(200);
|
||||
});
|
||||
|
||||
it('resolves 200 on matching static path - string params', async () => {
|
||||
// route provided with { params: { year: "2022", slug: "post-2" }}
|
||||
const res = await fixture.fetch('/blog/2022/post-1');
|
||||
expect(res.status).to.equal(200);
|
||||
});
|
||||
|
||||
it('resolves 200 on matching static path - numeric params', async () => {
|
||||
// route provided with { params: { year: 2022, slug: "post-2" }}
|
||||
const res = await fixture.fetch('/blog/2022/post-2');
|
||||
expect(res.status).to.equal(200);
|
||||
});
|
||||
});
|
||||
|
||||
describe('prerender getStaticPaths - numeric route params', () => {
|
||||
let fixture;
|
||||
let devServer;
|
||||
|
||||
before(async () => {
|
||||
// reset the flag used by [...calledTwiceTest].astro between each test
|
||||
globalThis.isCalledOnce = false;
|
||||
|
||||
fixture = await loadFixture({
|
||||
root: './fixtures/ssr-prerender-get-static-paths/',
|
||||
site: 'https://mysite.dev/',
|
||||
});
|
||||
devServer = await fixture.startDevServer();
|
||||
});
|
||||
|
||||
after(async () => {
|
||||
await devServer.stop();
|
||||
});
|
||||
|
||||
it('resolves 200 on matching static paths', async () => {
|
||||
// routes params provided for pages /posts/1, /posts/2, and /posts/3
|
||||
for (const page of [1, 2, 3]) {
|
||||
let res = await fixture.fetch(`/posts/${page}`);
|
||||
expect(res.status).to.equal(200);
|
||||
|
||||
const html = await res.text();
|
||||
const $ = cheerio.load(html);
|
||||
|
||||
const canonical = $('link[rel=canonical]');
|
||||
expect(canonical.attr('href')).to.equal(
|
||||
`https://mysite.dev/posts/${page}`,
|
||||
`doesn't trim the /${page} route param`
|
||||
);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
describe('prerender getStaticPaths - Astro.url', () => {
|
||||
/** @type {import('./test-utils').Fixture} */
|
||||
let fixture;
|
||||
before(async () => {
|
||||
// reset the flag used by [...calledTwiceTest].astro between each test
|
||||
globalThis.isCalledOnce = false;
|
||||
|
||||
fixture = await loadFixture({
|
||||
root: './fixtures/ssr-prerender-get-static-paths/',
|
||||
site: 'https://mysite.dev/',
|
||||
});
|
||||
await fixture.build();
|
||||
});
|
||||
|
||||
it('Sets the current pathname', async () => {
|
||||
const html = await fixture.readFile('/food/tacos/index.html');
|
||||
const $ = cheerio.load(html);
|
||||
|
||||
expect($('#url').text()).to.equal('/food/tacos/');
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
describe('prerender getStaticPaths - props', () => {
|
||||
/** @type {import('./test-utils').Fixture} */
|
||||
let fixture;
|
||||
before(async () => {
|
||||
// reset the flag used by [...calledTwiceTest].astro between each test
|
||||
globalThis.isCalledOnce = false;
|
||||
|
||||
fixture = await loadFixture({
|
||||
root: './fixtures/ssr-prerender-get-static-paths/',
|
||||
site: 'https://mysite.dev/',
|
||||
});
|
||||
await fixture.build();
|
||||
});
|
||||
|
||||
it('Sets the current pathname', async () => {
|
||||
const html = await fixture.readFile('/food/tacos/index.html');
|
||||
const $ = cheerio.load(html);
|
||||
|
||||
expect($('#props').text()).to.equal('10');
|
||||
});
|
||||
});
|
|
@ -1117,6 +1117,9 @@ importers:
|
|||
'@astrojs/node': link:../../../../integrations/node
|
||||
astro: link:../../..
|
||||
|
||||
packages/astro/test/benchmark/simple/dist/server:
|
||||
specifiers: {}
|
||||
|
||||
packages/astro/test/fixtures/0-css:
|
||||
specifiers:
|
||||
'@astrojs/react': workspace:*
|
||||
|
@ -2354,6 +2357,12 @@ importers:
|
|||
dependencies:
|
||||
astro: link:../../..
|
||||
|
||||
packages/astro/test/fixtures/ssr-prerender-get-static-paths:
|
||||
specifiers:
|
||||
astro: workspace:*
|
||||
dependencies:
|
||||
astro: link:../../..
|
||||
|
||||
packages/astro/test/fixtures/ssr-preview:
|
||||
specifiers:
|
||||
astro: workspace:*
|
||||
|
|
Loading…
Reference in a new issue