> {
+ return JSON.parse(await readFile(join(this.publishDir, 'server/pages-manifest.json'), 'utf-8'))
+ }
+
#nextVersion: string | null | undefined = undefined
/**
diff --git a/src/build/templates/handler.tmpl.js b/src/build/templates/handler.tmpl.js
index 0b10bcd902..1a089cb09f 100644
--- a/src/build/templates/handler.tmpl.js
+++ b/src/build/templates/handler.tmpl.js
@@ -44,6 +44,6 @@ export default async function handler(req, context) {
}
export const config = {
- path: '/*',
+ path: ['{{paths}}'],
preferStatic: true,
}
diff --git a/tests/fixtures/server-components/app/static-fetch-dynamic/page.js b/tests/fixtures/server-components/app/static-fetch-dynamic-1/page.js
similarity index 100%
rename from tests/fixtures/server-components/app/static-fetch-dynamic/page.js
rename to tests/fixtures/server-components/app/static-fetch-dynamic-1/page.js
diff --git a/tests/fixtures/server-components/app/static-fetch-dynamic/[id]/page.js b/tests/fixtures/server-components/app/static-fetch-dynamic/[id]/page.js
new file mode 100644
index 0000000000..85c8019450
--- /dev/null
+++ b/tests/fixtures/server-components/app/static-fetch-dynamic/[id]/page.js
@@ -0,0 +1,33 @@
+export async function generateStaticParams() {
+ return [{ id: '1' }, { id: '2' }]
+}
+
+async function getData(params) {
+ const res = await fetch(`https://api.tvmaze.com/shows/${params.id}`, {
+ next: {
+ tags: [`show-${params.id}`],
+ },
+ })
+ return res.json()
+}
+
+export default async function Page({ params }) {
+ const data = await getData(params)
+
+ return (
+ <>
+ Hello, Force Dynamically Rendered Server Component
+ Paths /1 and /2 prerendered; other paths not found
+
+ - Show
+ - {data.name}
+ - Param
+ - {params.id}
+ - Time
+ - {new Date().toISOString()}
+
+
+ )
+}
+
+export const dynamic = 'force-dynamic'
diff --git a/tests/fixtures/server-components/next-env.d.ts b/tests/fixtures/server-components/next-env.d.ts
index 4f11a03dc6..725dd6f245 100644
--- a/tests/fixtures/server-components/next-env.d.ts
+++ b/tests/fixtures/server-components/next-env.d.ts
@@ -1,5 +1,6 @@
///
///
+///
// NOTE: This file should not be edited
-// see https://nextjs.org/docs/basic-features/typescript for more information.
+// see https://nextjs.org/docs/app/building-your-application/configuring/typescript for more information.
diff --git a/tests/fixtures/server-components/pages/api/okay.js b/tests/fixtures/server-components/pages/api/okay.js
new file mode 100644
index 0000000000..45a65589e6
--- /dev/null
+++ b/tests/fixtures/server-components/pages/api/okay.js
@@ -0,0 +1,3 @@
+export default async function handler(req, res) {
+ return res.send({ code: 200, message: 'okay' })
+}
diff --git a/tests/fixtures/server-components/pages/api/posts/[id].js b/tests/fixtures/server-components/pages/api/posts/[id].js
new file mode 100644
index 0000000000..3fd01f0efb
--- /dev/null
+++ b/tests/fixtures/server-components/pages/api/posts/[id].js
@@ -0,0 +1,4 @@
+export default function handler(req, res) {
+ const { id } = req.query
+ res.send({ code: 200, message: `okay ${id}` })
+}
diff --git a/tests/fixtures/server-components/pages/dynamic.js b/tests/fixtures/server-components/pages/dynamic.js
new file mode 100644
index 0000000000..c0b8aae2ba
--- /dev/null
+++ b/tests/fixtures/server-components/pages/dynamic.js
@@ -0,0 +1,11 @@
+export default function Yar({ title }) {
+ return {title}
+}
+
+export async function getServerSideProps() {
+ return {
+ props: {
+ title: 'My Page',
+ },
+ }
+}
diff --git a/tests/fixtures/server-components/pages/posts/dynamic/[id].js b/tests/fixtures/server-components/pages/posts/dynamic/[id].js
new file mode 100644
index 0000000000..3b4b8d565d
--- /dev/null
+++ b/tests/fixtures/server-components/pages/posts/dynamic/[id].js
@@ -0,0 +1,25 @@
+export default function Page({ params }) {
+ return (
+ <>
+ Hello, Dyanmically fetched show
+
+ - Param
+ - {params.id}
+ - Time
+ - {new Date().toISOString()}
+
+
+ )
+}
+
+export async function getServerSideProps({ params }) {
+ const res = await fetch(`https://api.tvmaze.com/shows/${params.id}`)
+ const data = await res.json()
+
+ return {
+ props: {
+ params,
+ data,
+ },
+ }
+}
diff --git a/tests/fixtures/server-components/pages/posts/prerendered/[id].js b/tests/fixtures/server-components/pages/posts/prerendered/[id].js
new file mode 100644
index 0000000000..d797fbf74b
--- /dev/null
+++ b/tests/fixtures/server-components/pages/posts/prerendered/[id].js
@@ -0,0 +1,33 @@
+export default function Page({ params }) {
+ return (
+ <>
+ Hello, Statically fetched show
+ Paths /1 and /2 prerendered; other paths not found
+
+ - Param
+ - {params.id}
+ - Time
+ - {new Date().toISOString()}
+
+
+ )
+}
+
+export async function getStaticPaths() {
+ return {
+ paths: [{ params: { id: '1' } }, { params: { id: '2' } }],
+ fallback: false,
+ }
+}
+
+export async function getStaticProps({ params }) {
+ const res = await fetch(`https://api.tvmaze.com/shows/${params.id}`)
+ const data = await res.json()
+
+ return {
+ props: {
+ params,
+ data,
+ },
+ }
+}
diff --git a/tests/fixtures/server-components/pages/prerendered.js b/tests/fixtures/server-components/pages/prerendered.js
new file mode 100644
index 0000000000..520a3d0b96
--- /dev/null
+++ b/tests/fixtures/server-components/pages/prerendered.js
@@ -0,0 +1,11 @@
+export default function Yar({ title }) {
+ return {title}
+}
+
+export async function getStaticProps() {
+ return {
+ props: {
+ title: 'My Page',
+ },
+ }
+}
diff --git a/tests/fixtures/server-components/pages/static.js b/tests/fixtures/server-components/pages/static.js
new file mode 100644
index 0000000000..b6992717b0
--- /dev/null
+++ b/tests/fixtures/server-components/pages/static.js
@@ -0,0 +1,3 @@
+export default function Yup() {
+ return Yup
+}
diff --git a/tests/fixtures/server-components/tsconfig.json b/tests/fixtures/server-components/tsconfig.json
index b3222239ed..fc46f3d6cf 100644
--- a/tests/fixtures/server-components/tsconfig.json
+++ b/tests/fixtures/server-components/tsconfig.json
@@ -1,6 +1,10 @@
{
"compilerOptions": {
- "lib": ["dom", "dom.iterable", "esnext"],
+ "lib": [
+ "dom",
+ "dom.iterable",
+ "esnext"
+ ],
"allowJs": true,
"skipLibCheck": true,
"strict": false,
@@ -16,8 +20,16 @@
{
"name": "next"
}
- ]
+ ],
+ "strictNullChecks": true
},
- "include": ["next-env.d.ts", ".next/types/**/*.ts", "**/*.ts", "**/*.tsx"],
- "exclude": ["node_modules"]
+ "include": [
+ "next-env.d.ts",
+ ".next/types/**/*.ts",
+ "**/*.ts",
+ "**/*.tsx"
+ ],
+ "exclude": [
+ "node_modules"
+ ]
}
diff --git a/tests/integration/routing.test.ts b/tests/integration/routing.test.ts
new file mode 100644
index 0000000000..b807fa5481
--- /dev/null
+++ b/tests/integration/routing.test.ts
@@ -0,0 +1,70 @@
+import { join as posixJoin } from 'node:path/posix'
+import 'urlpattern-polyfill'
+import { v4 } from 'uuid'
+import { beforeEach, expect, test, vi } from 'vitest'
+import { SERVER_HANDLER_NAME } from '../../src/build/plugin-context.js'
+import { type FixtureTestContext } from '../utils/contexts.js'
+import { createFixture, runPlugin } from '../utils/fixture.js'
+import { generateRandomObjectID, startMockBlobStore } from '../utils/helpers.js'
+
+const ssrRoutes = [
+ ['/static', 'pages router, static rendering, static routing'],
+ ['/prerendered', 'pages router, prerendering, static routing'],
+ ['/posts/prerendered/1', 'pages router, prerendering, dynamic routing'],
+ ['/dynamic', 'pages router, dynamic rendering, static routing'],
+ ['/posts/dynamic/1', 'pages router, dynamic rendering, dynamic routing'],
+ ['/api/okay', 'pages router, api route, static routing'],
+ ['/api/posts/1', 'pages router, api route, dynamic routing'],
+ ['/static-fetch-1', 'app router, prerendering, static routing'],
+ ['/static-fetch/1', 'app router, prerendering, dynamic routing'],
+ ['/static-fetch-dynamic-1', 'app router, dynamic rendering, static routing'],
+ ['/static-fetch-dynamic/1', 'app router, dynamic rendering, dynamic routing'],
+ ['/api/revalidate-handler', 'app router, route handler, static routing'],
+ ['/api/static/1', 'app router, route handler, dynamic routing'],
+]
+
+const notFoundRoutes = [
+ ['/non-existing', 'default'],
+ ['/posts/prerendered/3', 'pages router, prerendering, dynamic routing'],
+ ['/api/non-existing', 'pages router, api route, static routing'],
+]
+
+const baseURL = 'http://localhost'
+
+beforeEach(async (ctx) => {
+ // set for each test a new deployID and siteID
+ ctx.deployID = generateRandomObjectID()
+ ctx.siteID = v4()
+ vi.stubEnv('DEPLOY_ID', ctx.deployID)
+
+ await startMockBlobStore(ctx)
+})
+
+test('that the SSR handler routing works correctly', async (ctx) => {
+ await createFixture('server-components', ctx)
+ await runPlugin(ctx)
+
+ const handler = await import(
+ posixJoin(
+ ctx.cwd,
+ '.netlify/functions-internal',
+ SERVER_HANDLER_NAME,
+ `${SERVER_HANDLER_NAME}.mjs`,
+ )
+ )
+
+ const matcher = (path: string) =>
+ handler.config.path.some((pattern) =>
+ new URLPattern({ pathname: pattern, baseURL }).test(posixJoin(baseURL, path)),
+ )
+
+ // check ssr routes are satisfied by the url patterns
+ for (const [path, description] of ssrRoutes) {
+ expect(path, `expected 200 response for ${description}`).toSatisfy(matcher)
+ }
+
+ // check not found routes are not satisfied by the url patterns
+ for (const [path, description] of notFoundRoutes) {
+ expect(path, `expected 404 response for ${description}`).not.toSatisfy(matcher)
+ }
+})