Feat: show 404 when getStaticPaths
doesn't match URL (#2743)
* WIP: return 404 for unmatched getStaticPaths route * feat: regex on static paths to 404 in dev * Revert "WIP: return 404 for unmatched getStaticPaths route" This reverts commit 9c395a2586ca40d44c3ab18edc7ffbc1c4660ed8. * feat: call getParamsAndProps pre-ssr to catch errs * fix: remove unused cache regex check * fix: revert getPattern changes * fix: remove unused preload props * fix: log 404 for custom 404 pages * refactor: rename fixture for clarity * feat: add getStaticPaths status code tests * fix: pas rootRelativeUrl to handle subpaths * fix: update dev-routing tests from 500 -> 404 * refactor: make error handling more explicit * lint: use typescript no shadow to fix enum issue * chore: add changeset * refactor: clarify test names * refactor: remove variable reassignment * fix: update dev-routing tests 500 > 404 * refactor: update test file structure * Fix: revert to old logging Co-authored-by: Nate Moore <natemoo-re@users.noreply.github.com> * Chore: use `const enum` instead Co-authored-by: Nate Moore <natemoo-re@users.noreply.github.com> * chore: format Co-authored-by: Nate Moore <nate@skypack.dev> Co-authored-by: Nate Moore <natemoo-re@users.noreply.github.com>
This commit is contained in:
parent
7f99d0de9e
commit
a14075e2a4
11 changed files with 175 additions and 52 deletions
5
.changeset/young-rules-draw.md
Normal file
5
.changeset/young-rules-draw.md
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
---
|
||||||
|
'astro': patch
|
||||||
|
---
|
||||||
|
|
||||||
|
Fix - show 404 for bad static paths with console message, rather than a 500
|
|
@ -14,8 +14,9 @@ module.exports = {
|
||||||
'@typescript-eslint/no-var-requires': 'off',
|
'@typescript-eslint/no-var-requires': 'off',
|
||||||
'@typescript-eslint/no-this-alias': 'off',
|
'@typescript-eslint/no-this-alias': 'off',
|
||||||
'no-console': 'warn',
|
'no-console': 'warn',
|
||||||
'no-shadow': 'error',
|
|
||||||
'prefer-const': 'off',
|
'prefer-const': 'off',
|
||||||
|
'no-shadow': 'off',
|
||||||
|
'@typescript-eslint/no-shadow': ['error'],
|
||||||
// 'require-jsdoc': 'error', // re-enable this to enforce JSDoc for all functions
|
// 'require-jsdoc': 'error', // re-enable this to enforce JSDoc for all functions
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
|
@ -44,12 +44,6 @@ export async function collectPagesData(opts: CollectPagesDataOptions): Promise<C
|
||||||
preload: await ssrPreload({
|
preload: await ssrPreload({
|
||||||
astroConfig,
|
astroConfig,
|
||||||
filePath: new URL(`./${route.component}`, astroConfig.projectRoot),
|
filePath: new URL(`./${route.component}`, astroConfig.projectRoot),
|
||||||
logging,
|
|
||||||
mode: 'production',
|
|
||||||
origin,
|
|
||||||
pathname: route.pathname,
|
|
||||||
route,
|
|
||||||
routeCache,
|
|
||||||
viteServer,
|
viteServer,
|
||||||
})
|
})
|
||||||
.then((routes) => {
|
.then((routes) => {
|
||||||
|
@ -106,12 +100,6 @@ export async function collectPagesData(opts: CollectPagesDataOptions): Promise<C
|
||||||
preload: await ssrPreload({
|
preload: await ssrPreload({
|
||||||
astroConfig,
|
astroConfig,
|
||||||
filePath: new URL(`./${route.component}`, astroConfig.projectRoot),
|
filePath: new URL(`./${route.component}`, astroConfig.projectRoot),
|
||||||
logging,
|
|
||||||
mode: 'production',
|
|
||||||
origin,
|
|
||||||
pathname: finalPaths[0],
|
|
||||||
route,
|
|
||||||
routeCache,
|
|
||||||
viteServer,
|
viteServer,
|
||||||
}),
|
}),
|
||||||
};
|
};
|
||||||
|
|
|
@ -15,7 +15,11 @@ interface GetParamsAndPropsOptions {
|
||||||
logging: LogOptions;
|
logging: LogOptions;
|
||||||
}
|
}
|
||||||
|
|
||||||
async function getParamsAndProps(opts: GetParamsAndPropsOptions): Promise<[Params, Props]> {
|
export const enum GetParamsAndPropsError {
|
||||||
|
NoMatchingStaticPath,
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function getParamsAndProps(opts: GetParamsAndPropsOptions): Promise<[Params, Props] | GetParamsAndPropsError> {
|
||||||
const { logging, mod, route, routeCache, pathname } = opts;
|
const { logging, mod, route, routeCache, pathname } = opts;
|
||||||
// Handle dynamic routes
|
// Handle dynamic routes
|
||||||
let params: Params = {};
|
let params: Params = {};
|
||||||
|
@ -38,7 +42,7 @@ async function getParamsAndProps(opts: GetParamsAndPropsOptions): Promise<[Param
|
||||||
}
|
}
|
||||||
const matchedStaticPath = findPathItemByKey(routeCacheEntry.staticPaths, params);
|
const matchedStaticPath = findPathItemByKey(routeCacheEntry.staticPaths, params);
|
||||||
if (!matchedStaticPath) {
|
if (!matchedStaticPath) {
|
||||||
throw new Error(`[getStaticPaths] route pattern matched, but no matching static path found. (${pathname})`);
|
return GetParamsAndPropsError.NoMatchingStaticPath;
|
||||||
}
|
}
|
||||||
// This is written this way for performance; instead of spreading the props
|
// This is written this way for performance; instead of spreading the props
|
||||||
// which is O(n), create a new object that extends props.
|
// which is O(n), create a new object that extends props.
|
||||||
|
@ -68,7 +72,7 @@ interface RenderOptions {
|
||||||
export async function render(opts: RenderOptions): Promise<string> {
|
export async function render(opts: RenderOptions): Promise<string> {
|
||||||
const { legacyBuild, links, logging, origin, markdownRender, mod, pathname, scripts, renderers, resolve, route, routeCache, site } = opts;
|
const { legacyBuild, links, logging, origin, markdownRender, mod, pathname, scripts, renderers, resolve, route, routeCache, site } = opts;
|
||||||
|
|
||||||
const [params, pageProps] = await getParamsAndProps({
|
const paramsAndPropsRes = await getParamsAndProps({
|
||||||
logging,
|
logging,
|
||||||
mod,
|
mod,
|
||||||
route,
|
route,
|
||||||
|
@ -76,6 +80,11 @@ export async function render(opts: RenderOptions): Promise<string> {
|
||||||
pathname,
|
pathname,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
if (paramsAndPropsRes === GetParamsAndPropsError.NoMatchingStaticPath) {
|
||||||
|
throw new Error(`[getStaticPath] route pattern matched, but no matching static path found. (${pathname})`);
|
||||||
|
}
|
||||||
|
const [params, pageProps] = paramsAndPropsRes;
|
||||||
|
|
||||||
// For endpoints, render the content immediately without injecting scripts or styles
|
// For endpoints, render the content immediately without injecting scripts or styles
|
||||||
if (route?.type === 'endpoint') {
|
if (route?.type === 'endpoint') {
|
||||||
return renderEndpoint(mod as any as EndpointHandler, params);
|
return renderEndpoint(mod as any as EndpointHandler, params);
|
||||||
|
|
|
@ -36,7 +36,7 @@ export type ComponentPreload = [Renderer[], ComponentInstance];
|
||||||
|
|
||||||
const svelteStylesRE = /svelte\?svelte&type=style/;
|
const svelteStylesRE = /svelte\?svelte&type=style/;
|
||||||
|
|
||||||
export async function preload({ astroConfig, filePath, viteServer }: SSROptions): Promise<ComponentPreload> {
|
export async function preload({ astroConfig, filePath, viteServer }: Pick<SSROptions, 'astroConfig' | 'filePath' | 'viteServer'>): Promise<ComponentPreload> {
|
||||||
// Important: This needs to happen first, in case a renderer provides polyfills.
|
// Important: This needs to happen first, in case a renderer provides polyfills.
|
||||||
const renderers = await resolveRenderers(viteServer, astroConfig);
|
const renderers = await resolveRenderers(viteServer, astroConfig);
|
||||||
// Load the module from the Vite SSR Runtime.
|
// Load the module from the Vite SSR Runtime.
|
||||||
|
@ -173,9 +173,9 @@ export async function render(renderers: Renderer[], mod: ComponentInstance, ssrO
|
||||||
return content;
|
return content;
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function ssr(ssrOpts: SSROptions): Promise<string> {
|
export async function ssr(preloadedComponent: ComponentPreload, ssrOpts: SSROptions): Promise<string> {
|
||||||
try {
|
try {
|
||||||
const [renderers, mod] = await preload(ssrOpts);
|
const [renderers, mod] = preloadedComponent;
|
||||||
return await render(renderers, mod, ssrOpts); // note(drew): without "await", errors won’t get caught by errorHandler()
|
return await render(renderers, mod, ssrOpts); // note(drew): without "await", errors won’t get caught by errorHandler()
|
||||||
} catch (e: unknown) {
|
} catch (e: unknown) {
|
||||||
await errorHandler(e, { viteServer: ssrOpts.viteServer, filePath: ssrOpts.filePath });
|
await errorHandler(e, { viteServer: ssrOpts.viteServer, filePath: ssrOpts.filePath });
|
||||||
|
|
|
@ -1,11 +1,12 @@
|
||||||
import type * as vite from 'vite';
|
import type * as vite from 'vite';
|
||||||
import type http from 'http';
|
import type http from 'http';
|
||||||
import type { AstroConfig, ManifestData } from '../@types/astro';
|
import type { AstroConfig, ManifestData } from '../@types/astro';
|
||||||
import { info, error, LogOptions } from '../core/logger.js';
|
import { info, warn, error, LogOptions } from '../core/logger.js';
|
||||||
|
import { getParamsAndProps, GetParamsAndPropsError } from '../core/render/core.js';
|
||||||
import { createRouteManifest, matchRoute } from '../core/routing/index.js';
|
import { createRouteManifest, matchRoute } from '../core/routing/index.js';
|
||||||
import stripAnsi from 'strip-ansi';
|
import stripAnsi from 'strip-ansi';
|
||||||
import { createSafeError } from '../core/util.js';
|
import { createSafeError } from '../core/util.js';
|
||||||
import { ssr } from '../core/render/dev/index.js';
|
import { ssr, preload } from '../core/render/dev/index.js';
|
||||||
import * as msg from '../core/messages.js';
|
import * as msg from '../core/messages.js';
|
||||||
|
|
||||||
import notFoundTemplate, { subpathNotUsedTemplate } from '../template/4xx.js';
|
import notFoundTemplate, { subpathNotUsedTemplate } from '../template/4xx.js';
|
||||||
|
@ -63,6 +64,15 @@ async function handle500Response(viteServer: vite.ViteDevServer, origin: string,
|
||||||
writeHtmlResponse(res, 500, transformedHtml);
|
writeHtmlResponse(res, 500, transformedHtml);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function getCustom404Route(config: AstroConfig, manifest: ManifestData) {
|
||||||
|
const relPages = config.pages.href.replace(config.projectRoot.href, '');
|
||||||
|
return manifest.routes.find((r) => r.component === relPages + '404.astro');
|
||||||
|
}
|
||||||
|
|
||||||
|
function log404(logging: LogOptions, pathname: string) {
|
||||||
|
info(logging, 'serve', msg.req({ url: pathname, statusCode: 404 }));
|
||||||
|
}
|
||||||
|
|
||||||
/** The main logic to route dev server requests to pages in Astro. */
|
/** The main logic to route dev server requests to pages in Astro. */
|
||||||
async function handleRequest(
|
async function handleRequest(
|
||||||
routeCache: RouteCache,
|
routeCache: RouteCache,
|
||||||
|
@ -79,37 +89,73 @@ async function handleRequest(
|
||||||
const origin = `${viteServer.config.server.https ? 'https' : 'http'}://${req.headers.host}`;
|
const origin = `${viteServer.config.server.https ? 'https' : 'http'}://${req.headers.host}`;
|
||||||
const pathname = decodeURI(new URL(origin + req.url).pathname);
|
const pathname = decodeURI(new URL(origin + req.url).pathname);
|
||||||
const rootRelativeUrl = pathname.substring(devRoot.length - 1);
|
const rootRelativeUrl = pathname.substring(devRoot.length - 1);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
if (!pathname.startsWith(devRoot)) {
|
if (!pathname.startsWith(devRoot)) {
|
||||||
info(logging, 'serve', msg.req({ url: pathname, statusCode: 404 }));
|
log404(logging, pathname);
|
||||||
return handle404Response(origin, config, req, res);
|
return handle404Response(origin, config, req, res);
|
||||||
}
|
}
|
||||||
// Attempt to match the URL to a valid page route.
|
// Attempt to match the URL to a valid page route.
|
||||||
// If that fails, switch the response to a 404 response.
|
// If that fails, switch the response to a 404 response.
|
||||||
let route = matchRoute(rootRelativeUrl, manifest);
|
let route = matchRoute(rootRelativeUrl, manifest);
|
||||||
const statusCode = route ? 200 : 404;
|
const statusCode = route ? 200 : 404;
|
||||||
// If no match found, lookup a custom 404 page to render, if one exists.
|
|
||||||
if (!route) {
|
if (!route) {
|
||||||
const relPages = config.pages.href.replace(config.projectRoot.href, '');
|
log404(logging, pathname);
|
||||||
route = manifest.routes.find((r) => r.component === relPages + '404.astro');
|
const custom404 = getCustom404Route(config, manifest);
|
||||||
|
if (custom404) {
|
||||||
|
route = custom404;
|
||||||
|
} else {
|
||||||
|
return handle404Response(origin, config, req, res);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
// If still no match is found, respond with a generic 404 page.
|
|
||||||
if (!route) {
|
const filePath = new URL(`./${route.component}`, config.projectRoot);
|
||||||
info(logging, 'serve', msg.req({ url: pathname, statusCode: 404 }));
|
const preloadedComponent = await preload({ astroConfig: config, filePath, viteServer });
|
||||||
handle404Response(origin, config, req, res);
|
const [, mod] = preloadedComponent;
|
||||||
return;
|
// attempt to get static paths
|
||||||
|
// if this fails, we have a bad URL match!
|
||||||
|
const paramsAndPropsRes = await getParamsAndProps({
|
||||||
|
mod,
|
||||||
|
route,
|
||||||
|
routeCache,
|
||||||
|
pathname: rootRelativeUrl,
|
||||||
|
logging,
|
||||||
|
});
|
||||||
|
if (paramsAndPropsRes === GetParamsAndPropsError.NoMatchingStaticPath) {
|
||||||
|
warn(logging, 'getStaticPaths', `Route pattern matched, but no matching static path found. (${pathname})`);
|
||||||
|
log404(logging, pathname);
|
||||||
|
const routeCustom404 = getCustom404Route(config, manifest);
|
||||||
|
if (routeCustom404) {
|
||||||
|
const filePathCustom404 = new URL(`./${routeCustom404.component}`, config.projectRoot);
|
||||||
|
const preloadedCompCustom404 = await preload({ astroConfig: config, filePath: filePathCustom404, viteServer });
|
||||||
|
const html = await ssr(preloadedCompCustom404, {
|
||||||
|
astroConfig: config,
|
||||||
|
filePath: filePathCustom404,
|
||||||
|
logging,
|
||||||
|
mode: 'development',
|
||||||
|
origin,
|
||||||
|
pathname: rootRelativeUrl,
|
||||||
|
route: routeCustom404,
|
||||||
|
routeCache,
|
||||||
|
viteServer,
|
||||||
|
});
|
||||||
|
return writeHtmlResponse(res, statusCode, html);
|
||||||
|
} else {
|
||||||
|
return handle404Response(origin, config, req, res);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
// Route successfully matched! Render it.
|
|
||||||
const html = await ssr({
|
const html = await ssr(preloadedComponent, {
|
||||||
astroConfig: config,
|
astroConfig: config,
|
||||||
filePath: new URL(`./${route.component}`, config.projectRoot),
|
filePath,
|
||||||
logging,
|
logging,
|
||||||
mode: 'development',
|
mode: 'development',
|
||||||
origin,
|
origin,
|
||||||
pathname: rootRelativeUrl,
|
pathname: rootRelativeUrl,
|
||||||
route,
|
route,
|
||||||
routeCache: routeCache,
|
routeCache,
|
||||||
viteServer: viteServer,
|
viteServer,
|
||||||
});
|
});
|
||||||
writeHtmlResponse(res, statusCode, html);
|
writeHtmlResponse(res, statusCode, html);
|
||||||
} catch (_err: any) {
|
} catch (_err: any) {
|
||||||
|
|
|
@ -1,11 +1,9 @@
|
||||||
import { expect } from 'chai';
|
import { expect } from 'chai';
|
||||||
import { loadFixture } from './test-utils.js';
|
import { loadFixture } from './test-utils.js';
|
||||||
|
|
||||||
describe('getStaticPaths()', () => {
|
describe('getStaticPaths - build calls', () => {
|
||||||
let fixture;
|
|
||||||
|
|
||||||
before(async () => {
|
before(async () => {
|
||||||
fixture = await loadFixture({
|
const fixture = await loadFixture({
|
||||||
projectRoot: './fixtures/astro-get-static-paths/',
|
projectRoot: './fixtures/astro-get-static-paths/',
|
||||||
buildOptions: {
|
buildOptions: {
|
||||||
site: 'https://mysite.dev/blog/',
|
site: 'https://mysite.dev/blog/',
|
||||||
|
@ -14,9 +12,42 @@ describe('getStaticPaths()', () => {
|
||||||
});
|
});
|
||||||
await fixture.build();
|
await fixture.build();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('is only called once during build', () => {
|
it('is only called once during build', () => {
|
||||||
// useless expect; if build() throws in setup then this test fails
|
// useless expect; if build() throws in setup then this test fails
|
||||||
expect(true).to.equal(true);
|
expect(true).to.equal(true);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('getStaticPaths - 404 behavior', () => {
|
||||||
|
let fixture;
|
||||||
|
let devServer;
|
||||||
|
|
||||||
|
before(async () => {
|
||||||
|
fixture = await loadFixture({ projectRoot: './fixtures/astro-get-static-paths/' });
|
||||||
|
devServer = await fixture.startDevServer();
|
||||||
|
});
|
||||||
|
|
||||||
|
after(async () => {
|
||||||
|
devServer && 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);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
|
@ -38,9 +38,9 @@ describe('Development Routing', () => {
|
||||||
expect(response.status).to.equal(200);
|
expect(response.status).to.equal(200);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('500 when loading invalid dynamic route', async () => {
|
it('404 when loading invalid dynamic route', async () => {
|
||||||
const response = await fixture.fetch('/2');
|
const response = await fixture.fetch('/2');
|
||||||
expect(response.status).to.equal(500);
|
expect(response.status).to.equal(404);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -74,9 +74,9 @@ describe('Development Routing', () => {
|
||||||
expect(response.status).to.equal(200);
|
expect(response.status).to.equal(200);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('500 when loading invalid dynamic route', async () => {
|
it('404 when loading invalid dynamic route', async () => {
|
||||||
const response = await fixture.fetch('/2');
|
const response = await fixture.fetch('/2');
|
||||||
expect(response.status).to.equal(500);
|
expect(response.status).to.equal(404);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -120,9 +120,9 @@ describe('Development Routing', () => {
|
||||||
expect(response.status).to.equal(200);
|
expect(response.status).to.equal(200);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('500 when loading invalid dynamic route', async () => {
|
it('404 when loading invalid dynamic route', async () => {
|
||||||
const response = await fixture.fetch('/blog/2/');
|
const response = await fixture.fetch('/blog/2/');
|
||||||
expect(response.status).to.equal(500);
|
expect(response.status).to.equal(404);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -166,9 +166,9 @@ describe('Development Routing', () => {
|
||||||
expect(response.status).to.equal(200);
|
expect(response.status).to.equal(200);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('500 when loading invalid dynamic route', async () => {
|
it('404 when loading invalid dynamic route', async () => {
|
||||||
const response = await fixture.fetch('/blog/2/');
|
const response = await fixture.fetch('/blog/2/');
|
||||||
expect(response.status).to.equal(500);
|
expect(response.status).to.equal(404);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -5,9 +5,9 @@ export function getStaticPaths({ paginate }) {
|
||||||
}
|
}
|
||||||
globalThis.isCalledOnce = true;
|
globalThis.isCalledOnce = true;
|
||||||
return [
|
return [
|
||||||
{params: {test: 'a'}},
|
{params: {calledTwiceTest: 'a'}},
|
||||||
{params: {test: 'b'}},
|
{params: {calledTwiceTest: 'b'}},
|
||||||
{params: {test: 'c'}},
|
{params: {calledTwiceTest: 'c'}},
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
const { params } = Astro.request;
|
const { params } = Astro.request;
|
||||||
|
@ -15,7 +15,7 @@ const { params } = Astro.request;
|
||||||
|
|
||||||
<html>
|
<html>
|
||||||
<head>
|
<head>
|
||||||
<title>Page {params.test}</title>
|
<title>Page {params.calledTwiceTest}</title>
|
||||||
</head>
|
</head>
|
||||||
<body></body>
|
<body></body>
|
||||||
</html>
|
</html>
|
22
packages/astro/test/fixtures/astro-get-static-paths/src/pages/pizza/[...pizza].astro
vendored
Normal file
22
packages/astro/test/fixtures/astro-get-static-paths/src/pages/pizza/[...pizza].astro
vendored
Normal file
|
@ -0,0 +1,22 @@
|
||||||
|
---
|
||||||
|
export function getStaticPaths() {
|
||||||
|
return [{
|
||||||
|
params: { pizza: 'papa-johns' },
|
||||||
|
}, {
|
||||||
|
params: { pizza: 'dominos' },
|
||||||
|
}, {
|
||||||
|
params: { pizza: 'grimaldis/new-york' },
|
||||||
|
}]
|
||||||
|
}
|
||||||
|
const { pizza } = Astro.request.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>
|
21
packages/astro/test/fixtures/astro-get-static-paths/src/pages/pizza/[cheese]-[topping].astro
vendored
Normal file
21
packages/astro/test/fixtures/astro-get-static-paths/src/pages/pizza/[cheese]-[topping].astro
vendored
Normal file
|
@ -0,0 +1,21 @@
|
||||||
|
---
|
||||||
|
export function getStaticPaths() {
|
||||||
|
return [{
|
||||||
|
params: { cheese: 'mozzarella', topping: 'pepperoni' },
|
||||||
|
}, {
|
||||||
|
params: { cheese: 'provolone', topping: 'sausage' },
|
||||||
|
}]
|
||||||
|
}
|
||||||
|
const { cheese, topping } = Astro.request.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>
|
Loading…
Reference in a new issue