summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.changeset/lemon-eagles-worry.md5
-rw-r--r--packages/astro/src/@types/astro.ts1
-rw-r--r--packages/astro/src/core/build/generate.ts2
-rw-r--r--packages/astro/src/core/build/vite-plugin-ssr.ts2
-rw-r--r--packages/astro/src/core/errors/errors-data.ts2
-rw-r--r--packages/astro/src/core/render/route-cache.ts2
-rw-r--r--packages/astro/src/core/routing/validation.ts4
-rw-r--r--packages/astro/test/fixtures/ssr-prerender-get-static-paths/package.json8
-rw-r--r--packages/astro/test/fixtures/ssr-prerender-get-static-paths/src/pages/[...calledTwiceTest].astro22
-rw-r--r--packages/astro/test/fixtures/ssr-prerender-get-static-paths/src/pages/blog/[year]/[slug].astro19
-rw-r--r--packages/astro/test/fixtures/ssr-prerender-get-static-paths/src/pages/data/[slug].json.ts16
-rw-r--r--packages/astro/test/fixtures/ssr-prerender-get-static-paths/src/pages/food/[name].astro34
-rw-r--r--packages/astro/test/fixtures/ssr-prerender-get-static-paths/src/pages/nested-arrays/[slug].astro10
-rw-r--r--packages/astro/test/fixtures/ssr-prerender-get-static-paths/src/pages/pizza/[...pizza].astro23
-rw-r--r--packages/astro/test/fixtures/ssr-prerender-get-static-paths/src/pages/pizza/[cheese]-[topping].astro22
-rw-r--r--packages/astro/test/fixtures/ssr-prerender-get-static-paths/src/pages/posts/[page].astro30
-rw-r--r--packages/astro/test/ssr-prerender-get-static-paths.test.js202
-rw-r--r--pnpm-lock.yaml9
18 files changed, 408 insertions, 5 deletions
diff --git a/.changeset/lemon-eagles-worry.md b/.changeset/lemon-eagles-worry.md
new file mode 100644
index 000000000..9a5671b00
--- /dev/null
+++ b/.changeset/lemon-eagles-worry.md
@@ -0,0 +1,5 @@
+---
+'astro': patch
+---
+
+Fix `prerender` when used with `getStaticPaths`
diff --git a/packages/astro/src/@types/astro.ts b/packages/astro/src/@types/astro.ts
index 866c2eaf0..8d735bf23 100644
--- a/packages/astro/src/@types/astro.ts
+++ b/packages/astro/src/@types/astro.ts
@@ -1004,6 +1004,7 @@ export type AsyncRendererComponentFn<U> = (
export interface ComponentInstance {
default: AstroComponentFactory;
css?: string[];
+ prerender?: boolean;
getStaticPaths?: (options: GetStaticPathsOptions) => GetStaticPathsResult;
}
diff --git a/packages/astro/src/core/build/generate.ts b/packages/astro/src/core/build/generate.ts
index b4c8ee8f0..0dd1a1bba 100644
--- a/packages/astro/src/core/build/generate.ts
+++ b/packages/astro/src/core/build/generate.ts
@@ -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';
diff --git a/packages/astro/src/core/build/vite-plugin-ssr.ts b/packages/astro/src/core/build/vite-plugin-ssr.ts
index 9de0051fc..be8280f38 100644
--- a/packages/astro/src/core/build/vite-plugin-ssr.ts
+++ b/packages/astro/src/core/build/vite-plugin-ssr.ts
@@ -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!,
diff --git a/packages/astro/src/core/errors/errors-data.ts b/packages/astro/src/core/errors/errors-data.ts
index e8a788f27..669c5054f 100644
--- a/packages/astro/src/core/errors/errors-data.ts
+++ b/packages/astro/src/core/errors/errors-data.ts
@@ -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.`,
},
/**
diff --git a/packages/astro/src/core/render/route-cache.ts b/packages/astro/src/core/render/route-cache.ts
index b46e87c6e..e6ef87107 100644
--- a/packages/astro/src/core/render/route-cache.ts
+++ b/packages/astro/src/core/render/route-cache.ts
@@ -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.
diff --git a/packages/astro/src/core/routing/validation.ts b/packages/astro/src/core/routing/validation.ts
index 9e13764b0..047a5b892 100644
--- a/packages/astro/src/core/routing/validation.ts
+++ b/packages/astro/src/core/routing/validation.ts
@@ -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 },
diff --git a/packages/astro/test/fixtures/ssr-prerender-get-static-paths/package.json b/packages/astro/test/fixtures/ssr-prerender-get-static-paths/package.json
new file mode 100644
index 000000000..f04af3686
--- /dev/null
+++ b/packages/astro/test/fixtures/ssr-prerender-get-static-paths/package.json
@@ -0,0 +1,8 @@
+{
+ "name": "@test/ssr-prerender-get-static-paths",
+ "version": "0.0.0",
+ "private": true,
+ "dependencies": {
+ "astro": "workspace:*"
+ }
+}
diff --git a/packages/astro/test/fixtures/ssr-prerender-get-static-paths/src/pages/[...calledTwiceTest].astro b/packages/astro/test/fixtures/ssr-prerender-get-static-paths/src/pages/[...calledTwiceTest].astro
new file mode 100644
index 000000000..cffb6c195
--- /dev/null
+++ b/packages/astro/test/fixtures/ssr-prerender-get-static-paths/src/pages/[...calledTwiceTest].astro
@@ -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>
diff --git a/packages/astro/test/fixtures/ssr-prerender-get-static-paths/src/pages/blog/[year]/[slug].astro b/packages/astro/test/fixtures/ssr-prerender-get-static-paths/src/pages/blog/[year]/[slug].astro
new file mode 100644
index 000000000..7e3edfb26
--- /dev/null
+++ b/packages/astro/test/fixtures/ssr-prerender-get-static-paths/src/pages/blog/[year]/[slug].astro
@@ -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>
diff --git a/packages/astro/test/fixtures/ssr-prerender-get-static-paths/src/pages/data/[slug].json.ts b/packages/astro/test/fixtures/ssr-prerender-get-static-paths/src/pages/data/[slug].json.ts
new file mode 100644
index 000000000..16e2a90ca
--- /dev/null
+++ b/packages/astro/test/fixtures/ssr-prerender-get-static-paths/src/pages/data/[slug].json.ts
@@ -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)
+ };
+}
diff --git a/packages/astro/test/fixtures/ssr-prerender-get-static-paths/src/pages/food/[name].astro b/packages/astro/test/fixtures/ssr-prerender-get-static-paths/src/pages/food/[name].astro
new file mode 100644
index 000000000..dd19d965d
--- /dev/null
+++ b/packages/astro/test/fixtures/ssr-prerender-get-static-paths/src/pages/food/[name].astro
@@ -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>
diff --git a/packages/astro/test/fixtures/ssr-prerender-get-static-paths/src/pages/nested-arrays/[slug].astro b/packages/astro/test/fixtures/ssr-prerender-get-static-paths/src/pages/nested-arrays/[slug].astro
new file mode 100644
index 000000000..25d1bfff4
--- /dev/null
+++ b/packages/astro/test/fixtures/ssr-prerender-get-static-paths/src/pages/nested-arrays/[slug].astro
@@ -0,0 +1,10 @@
+---
+ export function getStaticPaths() {
+ return [
+ [ { params: {slug: "slug1"} } ],
+ [ { params: {slug: "slug2"} } ],
+ ]
+ }
+
+ export const prerender = true;
+---
diff --git a/packages/astro/test/fixtures/ssr-prerender-get-static-paths/src/pages/pizza/[...pizza].astro b/packages/astro/test/fixtures/ssr-prerender-get-static-paths/src/pages/pizza/[...pizza].astro
new file mode 100644
index 000000000..8df0634b1
--- /dev/null
+++ b/packages/astro/test/fixtures/ssr-prerender-get-static-paths/src/pages/pizza/[...pizza].astro
@@ -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>
diff --git a/packages/astro/test/fixtures/ssr-prerender-get-static-paths/src/pages/pizza/[cheese]-[topping].astro b/packages/astro/test/fixtures/ssr-prerender-get-static-paths/src/pages/pizza/[cheese]-[topping].astro
new file mode 100644
index 000000000..11f2dd377
--- /dev/null
+++ b/packages/astro/test/fixtures/ssr-prerender-get-static-paths/src/pages/pizza/[cheese]-[topping].astro
@@ -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>
diff --git a/packages/astro/test/fixtures/ssr-prerender-get-static-paths/src/pages/posts/[page].astro b/packages/astro/test/fixtures/ssr-prerender-get-static-paths/src/pages/posts/[page].astro
new file mode 100644
index 000000000..46865e7f4
--- /dev/null
+++ b/packages/astro/test/fixtures/ssr-prerender-get-static-paths/src/pages/posts/[page].astro
@@ -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>
diff --git a/packages/astro/test/ssr-prerender-get-static-paths.test.js b/packages/astro/test/ssr-prerender-get-static-paths.test.js
new file mode 100644
index 000000000..e7e4b96de
--- /dev/null
+++ b/packages/astro/test/ssr-prerender-get-static-paths.test.js
@@ -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');
+ });
+});
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index c776c775d..126fc2a6a 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -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:*