first commit
Some checks failed
Test examples / Test Examples (20) (push) Has been cancelled
Test examples / Test Examples (22) (push) Has been cancelled
Lock Threads / action (push) Has been cancelled
Trigger Release / start (push) Has been cancelled
Stale issue handler / stale (push) Has been cancelled
Update Font Data / create-pull-request (push) Has been cancelled
build-and-deploy / deploy-target (push) Has been cancelled
build-and-deploy / build (push) Has been cancelled
build-and-deploy / stable - aarch64-unknown-linux-musl - node@16 (push) Has been cancelled
build-and-deploy / stable - x86_64-unknown-linux-musl - node@16 (push) Has been cancelled
build-and-deploy / stable - aarch64-unknown-linux-gnu - node@16 (push) Has been cancelled
build-and-deploy / stable - x86_64-unknown-linux-gnu - node@16 (push) Has been cancelled
build-and-deploy / stable - aarch64-pc-windows-msvc - node@16 (push) Has been cancelled
build-and-deploy / stable - x86_64-pc-windows-msvc - node@16 (push) Has been cancelled
build-and-deploy / stable - aarch64-apple-darwin - node@16 (push) Has been cancelled
build-and-deploy / stable - x86_64-apple-darwin - node@16 (push) Has been cancelled
build-and-deploy / build-wasm (nodejs) (push) Has been cancelled
build-and-deploy / build-wasm (web) (push) Has been cancelled
build-and-deploy / Deploy preview tarball (push) Has been cancelled
build-and-deploy / Potentially publish release (push) Has been cancelled
build-and-deploy / publish-turbopack-npm-packages (push) Has been cancelled
build-and-deploy / Deploy examples (push) Has been cancelled
build-and-deploy / thank you, build (push) Has been cancelled
build-and-deploy / Upload Turbopack Bytesize metrics to Datadog (push) Has been cancelled
Rspack Next.js development integration tests / Rspack integration tests (push) Has been cancelled
Rspack Next.js production integration tests / Rspack integration tests (push) Has been cancelled
Turbopack Next.js development integration tests / Next.js integration tests (push) Has been cancelled
Turbopack Next.js production integration tests / Next.js integration tests (push) Has been cancelled
Update Rspack test manifest / Update and upload Rspack development test manifest (push) Has been cancelled
Update Rspack test manifest / Update and upload Rspack production test manifest (push) Has been cancelled
Upload bundler test manifests to areweturboyet.com / Upload test results (push) Has been cancelled
Update React / create-pull-request (push) Has been cancelled
test-e2e-project-reset-cron / reset-test-project (push) Has been cancelled
Notify about the top 15 issues/PRs/feature requests (most reacted) in the last 90 days / run (push) Has been cancelled

This commit is contained in:
Arian Tron
2026-03-10 19:37:31 +03:30
commit 61f56f997c
27684 changed files with 2784175 additions and 0 deletions

View File

@@ -0,0 +1 @@
module.exports = {}

View File

@@ -0,0 +1,24 @@
import { useRouter } from 'next/router'
export async function getServerSideProps({ query }) {
return {
props: {
query,
},
}
}
export default function Page(props) {
return (
<>
<div id="route">
top level route param:{' '}
{props.query.optionalName === undefined
? 'undefined'
: `[${props.query.optionalName.join('|')}]`}
</div>
<div id="keys">{JSON.stringify(Object.keys(props.query))}</div>
<div id="asPath">{useRouter().asPath}</div>
</>
)
}

View File

@@ -0,0 +1,9 @@
export default function Page(props) {
return <div id="content">about</div>
}
export const getServerSideProps = () => {
return {
props: {},
}
}

View File

@@ -0,0 +1 @@
export default (req, res) => res.json({ slug: req.query.slug })

View File

@@ -0,0 +1,35 @@
import { useRouter } from 'next/router'
export async function getStaticPaths() {
return {
paths: [
{
params: { slug: [] },
},
{
params: { slug: ['p1'] },
},
{
params: { slug: ['p2', 'p3'] },
},
],
fallback: true,
}
}
export async function getStaticProps({ params }) {
return { props: { params } }
}
export default function Index(props) {
const router = useRouter()
return (
<div id="route">
gsp fallback route:{' '}
{props.params?.slug === undefined
? 'undefined'
: `[${props.params.slug.join('|')}]`}
{router.isFallback ? ' is fallback' : ' is not fallback'}
</div>
)
}

View File

@@ -0,0 +1,25 @@
export async function getStaticPaths() {
return {
paths: [
{
params: { slug: false },
},
],
fallback: false,
}
}
export async function getStaticProps({ params }) {
return { props: { params } }
}
export default function Index(props) {
return (
<div id="route">
gsp false route:{' '}
{props.params.slug === undefined
? 'undefined'
: `[${props.params.slug.join('|')}]`}
</div>
)
}

View File

@@ -0,0 +1,25 @@
export async function getStaticPaths() {
return {
paths: [
{
params: { slug: null },
},
],
fallback: false,
}
}
export async function getStaticProps({ params }) {
return { props: { params } }
}
export default function Index(props) {
return (
<div id="route">
gsp null route:{' '}
{props.params.slug === undefined
? 'undefined'
: `[${props.params.slug.join('|')}]`}
</div>
)
}

View File

@@ -0,0 +1,25 @@
export async function getStaticPaths() {
return {
paths: [
{
params: { slug: undefined },
},
],
fallback: false,
}
}
export async function getStaticProps({ params }) {
return { props: { params } }
}
export default function Index(props) {
return (
<div id="route">
gsp undefined route:{' '}
{props.params.slug === undefined
? 'undefined'
: `[${props.params.slug.join('|')}]`}
</div>
)
}

View File

@@ -0,0 +1,31 @@
export async function getStaticPaths() {
return {
paths: [
{
params: { slug: [] },
},
{
params: { slug: ['p1'] },
},
{
params: { slug: ['p2', 'p3'] },
},
],
fallback: false,
}
}
export async function getStaticProps({ params }) {
return { props: { params } }
}
export default function Index(props) {
return (
<div id="route">
gsp route:{' '}
{props.params.slug === undefined
? 'undefined'
: `[${props.params.slug.join('|')}]`}
</div>
)
}

View File

@@ -0,0 +1,24 @@
import { useRouter } from 'next/router'
export async function getServerSideProps({ query }) {
return {
props: {
query,
},
}
}
export default function Page(props) {
return (
<>
<div id="route">
nested route param:{' '}
{props.query.optionalName === undefined
? 'undefined'
: `[${props.query.optionalName.join('|')}]`}
</div>
<div id="keys">{JSON.stringify(Object.keys(props.query))}</div>
<div id="asPath">{useRouter().asPath}</div>
</>
)
}

View File

@@ -0,0 +1,325 @@
/* eslint-env jest */
import cheerio from 'cheerio'
import fs from 'fs-extra'
import {
fetchViaHTTP,
findPort,
killApp,
launchApp,
nextBuild,
nextStart,
renderViaHTTP,
check,
} from 'next-test-utils'
import { join } from 'path'
let app
let appPort
let stderr
const appDir = join(__dirname, '../')
const DUMMY_PAGE = 'export default () => null'
function runTests() {
it('should render catch-all top-level route with multiple segments', async () => {
const html = await renderViaHTTP(appPort, '/hello/world')
const $ = cheerio.load(html)
expect($('#route').text()).toBe('top level route param: [hello|world]')
})
it('should render catch-all top-level route with single segment', async () => {
const html = await renderViaHTTP(appPort, '/hello')
const $ = cheerio.load(html)
expect($('#route').text()).toBe('top level route param: [hello]')
})
it('should render catch-all top-level route with no segments', async () => {
const html = await renderViaHTTP(appPort, '/')
const $ = cheerio.load(html)
expect($('#route').text()).toBe('top level route param: undefined')
})
it('should render catch-all nested route with multiple segments', async () => {
const html = await renderViaHTTP(appPort, '/nested/hello/world')
const $ = cheerio.load(html)
expect($('#route').text()).toBe('nested route param: [hello|world]')
})
it('should render catch-all nested route with single segment', async () => {
const html = await renderViaHTTP(appPort, '/nested/hello')
const $ = cheerio.load(html)
expect($('#route').text()).toBe('nested route param: [hello]')
})
it('should render catch-all nested route with no segments', async () => {
const html = await renderViaHTTP(appPort, '/nested')
const $ = cheerio.load(html)
expect($('#route').text()).toBe('nested route param: undefined')
})
it('should render catch-all nested route with no segments and leading slash', async () => {
const html = await renderViaHTTP(appPort, '/nested/')
const $ = cheerio.load(html)
expect($('#route').text()).toBe('nested route param: undefined')
})
it('should match catch-all api route with multiple segments', async () => {
const res = await fetchViaHTTP(appPort, '/api/post/ab/cd')
expect(res.status).toBe(200)
expect(await res.json()).toEqual({ slug: ['ab', 'cd'] })
})
it('should match catch-all api route with single segment', async () => {
const res = await fetchViaHTTP(appPort, '/api/post/a')
expect(res.status).toBe(200)
expect(await res.json()).toEqual({ slug: ['a'] })
})
it('should match catch-all api route with no segments', async () => {
const res = await fetchViaHTTP(appPort, '/api/post')
expect(res.status).toBe(200)
expect(await res.json()).toEqual({})
})
it('should match catch-all api route with no segments and leading slash', async () => {
const res = await fetchViaHTTP(appPort, '/api/post/')
expect(res.status).toBe(200)
expect(await res.json()).toEqual({})
})
it('should handle getStaticPaths no segments', async () => {
const html = await renderViaHTTP(appPort, '/get-static-paths')
const $ = cheerio.load(html)
expect($('#route').text()).toBe('gsp route: undefined')
})
it('should handle getStaticPaths no segments and trailing slash', async () => {
const html = await renderViaHTTP(appPort, '/get-static-paths/')
const $ = cheerio.load(html)
expect($('#route').text()).toBe('gsp route: undefined')
})
it('should handle getStaticPaths 1 segment', async () => {
const html = await renderViaHTTP(appPort, '/get-static-paths/p1')
const $ = cheerio.load(html)
expect($('#route').text()).toBe('gsp route: [p1]')
})
it('should handle getStaticPaths 1 segment and trailing slash', async () => {
const html = await renderViaHTTP(appPort, '/get-static-paths/p1/')
const $ = cheerio.load(html)
expect($('#route').text()).toBe('gsp route: [p1]')
})
it('should handle getStaticPaths 2 segments', async () => {
const html = await renderViaHTTP(appPort, '/get-static-paths/p2/p3')
const $ = cheerio.load(html)
expect($('#route').text()).toBe('gsp route: [p2|p3]')
})
it('should handle getStaticPaths 2 segments and trailing slash', async () => {
const html = await renderViaHTTP(appPort, '/get-static-paths/p2/p3/')
const $ = cheerio.load(html)
expect($('#route').text()).toBe('gsp route: [p2|p3]')
})
it('should fall back to top-level catch-all', async () => {
const html = await renderViaHTTP(appPort, '/get-static-paths/hello/world')
const $ = cheerio.load(html)
expect($('#route').text()).toBe(
'top level route param: [get-static-paths|hello|world]'
)
})
it('should match root path on undefined param', async () => {
const html = await renderViaHTTP(appPort, '/get-static-paths-undefined')
const $ = cheerio.load(html)
expect($('#route').text()).toBe('gsp undefined route: undefined')
})
it('should match root path on false param', async () => {
const html = await renderViaHTTP(appPort, '/get-static-paths-false')
const $ = cheerio.load(html)
expect($('#route').text()).toBe('gsp false route: undefined')
})
it('should match root path on null param', async () => {
const html = await renderViaHTTP(appPort, '/get-static-paths-null')
const $ = cheerio.load(html)
expect($('#route').text()).toBe('gsp null route: undefined')
})
it('should handle getStaticPaths with fallback no segments', async () => {
const html = await renderViaHTTP(appPort, '/get-static-paths-fallback')
const $ = cheerio.load(html)
expect($('#route').text()).toBe(
'gsp fallback route: undefined is not fallback'
)
})
it('should handle getStaticPaths with fallback 2 segments', async () => {
const html = await renderViaHTTP(
appPort,
'/get-static-paths-fallback/p2/p3'
)
const $ = cheerio.load(html)
expect($('#route').text()).toBe(
'gsp fallback route: [p2|p3] is not fallback'
)
})
it('should fallback correctly when fallback enabled', async () => {
const html = await renderViaHTTP(
appPort,
'/get-static-paths-fallback/hello/world'
)
const $ = cheerio.load(html)
expect($('#route').text()).toBe('gsp fallback route: undefined is fallback')
})
}
const nextConfig = join(appDir, 'next.config.js')
function runInvalidPagesTests(buildFn) {
it('should fail to build when optional route has index.js at root', async () => {
const invalidRoute = appDir + 'pages/index.js'
try {
await fs.outputFile(invalidRoute, DUMMY_PAGE, 'utf-8')
await buildFn(appDir)
await check(
() => stderr,
/You cannot define a route with the same specificity as a optional catch-all route/
)
} finally {
await fs.unlink(invalidRoute)
}
})
it('should fail to build when optional route has same page at root', async () => {
const invalidRoute = appDir + 'pages/nested.js'
try {
await fs.outputFile(invalidRoute, DUMMY_PAGE, 'utf-8')
await buildFn(appDir)
await check(
() => stderr,
/You cannot define a route with the same specificity as a optional catch-all route/
)
} finally {
await fs.unlink(invalidRoute)
}
})
it('should fail to build when mixed with regular catch-all', async () => {
const invalidRoute = appDir + 'pages/nested/[...param].js'
try {
await fs.outputFile(invalidRoute, DUMMY_PAGE, 'utf-8')
await buildFn(appDir)
await check(() => stderr, /You cannot use both .+ at the same level/)
} finally {
await fs.unlink(invalidRoute)
}
})
it('should fail to build when optional but no catch-all', async () => {
const invalidRoute = appDir + 'pages/invalid/[[param]].js'
try {
await fs.outputFile(invalidRoute, DUMMY_PAGE, 'utf-8')
await buildFn(appDir)
await check(
() => stderr,
/Optional route parameters are not yet supported/
)
} finally {
await fs.unlink(invalidRoute)
}
})
}
describe('Dynamic Optional Routing', () => {
;(process.env.TURBOPACK_BUILD ? describe.skip : describe)(
'development mode',
() => {
describe('rendering', () => {
beforeAll(async () => {
appPort = await findPort()
app = await launchApp(appDir, appPort)
})
afterAll(() => killApp(app))
runTests()
})
describe('invalid pages', () => {
runInvalidPagesTests(async (appDir) => {
stderr = ''
appPort = await findPort()
app = await launchApp(appDir, appPort, {
onStderr: (msg) => {
stderr += msg
},
})
})
afterEach(() => killApp(app))
})
}
)
;(process.env.TURBOPACK_DEV ? describe.skip : describe)(
'production mode',
() => {
beforeAll(async () => {
const curConfig = await fs.readFile(nextConfig, 'utf8')
if (curConfig.includes('target')) {
await fs.writeFile(nextConfig, `module.exports = {}`)
}
await nextBuild(appDir)
appPort = await findPort()
app = await nextStart(appDir, appPort)
})
afterAll(() => killApp(app))
runTests()
runInvalidPagesTests(async (appDir) => {
;({ stderr } = await nextBuild(appDir, [], { stderr: true }))
})
it('should fail to build when param is not explicitly defined', async () => {
const invalidRoute = appDir + 'pages/invalid/[[...slug]].js'
try {
await fs.outputFile(
invalidRoute,
`
export async function getStaticPaths() {
return {
paths: [
{ params: {} },
],
fallback: false,
}
}
export async function getStaticProps({ params }) {
return { props: { params } }
}
export default function Index(props) {
return (
<div>Invalid</div>
)
}
`,
'utf-8'
)
const { stderr } = await nextBuild(appDir, [], { stderr: true })
await expect(stderr).toMatch(
'A required parameter (slug) was not provided as an array received undefined in getStaticPaths for /invalid/[[...slug]]'
)
} finally {
await fs.unlink(invalidRoute)
}
})
}
)
})