diff --git a/packages/next/export/worker.ts b/packages/next/export/worker.ts index 180def8d1cd2..61088d25e684 100644 --- a/packages/next/export/worker.ts +++ b/packages/next/export/worker.ts @@ -215,8 +215,10 @@ export default async function exportPage({ subFolders ? `${_path}${sep}index.html` : `${_path}.html` let htmlFilename = getHtmlFilename(filePath) - const pageExt = extname(page) - const pathExt = extname(path) + // dynamic routes can provide invalid extensions e.g. /blog/[...slug] returns an + // extension of `.slug]` + const pageExt = isDynamic ? '' : extname(page) + const pathExt = isDynamic ? '' : extname(path) // Make sure page isn't a folder with a dot in the name e.g. `v1.2` if (pageExt !== pathExt && pathExt !== '') { const isBuiltinPaths = ['/500', '/404'].some( diff --git a/packages/next/server/denormalize-page-path.ts b/packages/next/server/denormalize-page-path.ts index 39ba36212f69..e3e2f1bcecd0 100644 --- a/packages/next/server/denormalize-page-path.ts +++ b/packages/next/server/denormalize-page-path.ts @@ -1,10 +1,12 @@ +import { isDynamicRoute } from '../shared/lib/router/utils' + export function normalizePathSep(path: string): string { return path.replace(/\\/g, '/') } export function denormalizePagePath(page: string) { page = normalizePathSep(page) - if (page.startsWith('/index/')) { + if (page.startsWith('/index/') && !isDynamicRoute(page)) { page = page.slice(6) } else if (page === '/index') { page = '/' diff --git a/packages/next/server/normalize-page-path.ts b/packages/next/server/normalize-page-path.ts index 95e8d9a596c6..f9996331e3be 100644 --- a/packages/next/server/normalize-page-path.ts +++ b/packages/next/server/normalize-page-path.ts @@ -1,4 +1,5 @@ import { posix } from 'path' +import { isDynamicRoute } from '../shared/lib/router/utils' export { normalizePathSep, denormalizePagePath } from './denormalize-page-path' @@ -6,7 +7,7 @@ export function normalizePagePath(page: string): string { // If the page is `/` we need to append `/index`, otherwise the returned directory root will be bundles instead of pages if (page === '/') { page = '/index' - } else if (/^\/index(\/|$)/.test(page)) { + } else if (/^\/index(\/|$)/.test(page) && !isDynamicRoute(page)) { page = `/index${page}` } // Resolve on anything that doesn't start with `/` diff --git a/packages/next/shared/lib/router/utils/get-route-from-asset-path.ts b/packages/next/shared/lib/router/utils/get-route-from-asset-path.ts index 495865e56876..b738e75c74f0 100644 --- a/packages/next/shared/lib/router/utils/get-route-from-asset-path.ts +++ b/packages/next/shared/lib/router/utils/get-route-from-asset-path.ts @@ -1,4 +1,7 @@ // Translate a pages asset path (relative from a common prefix) back into its logical route + +import { isDynamicRoute } from './is-dynamic' + // "asset path" being its javascript file, data file, prerendered html,... export default function getRouteFromAssetPath( assetPath: string, @@ -7,7 +10,7 @@ export default function getRouteFromAssetPath( assetPath = assetPath.replace(/\\/g, '/') assetPath = ext && assetPath.endsWith(ext) ? assetPath.slice(0, -ext.length) : assetPath - if (assetPath.startsWith('/index/')) { + if (assetPath.startsWith('/index/') && !isDynamicRoute(assetPath)) { assetPath = assetPath.slice(6) } else if (assetPath === '/index') { assetPath = '/' diff --git a/test/integration/dynamic-routing/pages/index/[...slug].js b/test/integration/dynamic-routing/pages/index/[...slug].js new file mode 100644 index 000000000000..0688517139fd --- /dev/null +++ b/test/integration/dynamic-routing/pages/index/[...slug].js @@ -0,0 +1,3 @@ +export default function page() { + return 'index/[...slug]' +} diff --git a/test/integration/dynamic-routing/test/index.test.js b/test/integration/dynamic-routing/test/index.test.js index 7be7e6b00a46..cc97e9a6b6b9 100644 --- a/test/integration/dynamic-routing/test/index.test.js +++ b/test/integration/dynamic-routing/test/index.test.js @@ -27,7 +27,7 @@ let buildId const appDir = join(__dirname, '../') const buildIdPath = join(appDir, '.next/BUILD_ID') -function runTests(dev) { +function runTests({ dev, serverless }) { if (dev) { it('should not have error after pinging WebSocket', async () => { const browser = await webdriver(appPort, '/') @@ -1256,6 +1256,14 @@ function runTests(dev) { helloworld: 'hello-world', }, }, + { + namedRegex: '^/index/(?.+?)(?:/)?$', + page: '/index/[...slug]', + regex: normalizeRegEx('^/index/(.+?)(?:/)?$'), + routeKeys: { + slug: 'slug', + }, + }, { namedRegex: `^/on\\-mount/(?[^/]+?)(?:/)?$`, page: '/on-mount/[post]', @@ -1338,6 +1346,43 @@ function runTests(dev) { ], }) }) + + if (!serverless) { + it('should output a pages-manifest correctly', async () => { + const manifest = await fs.readJson( + join(appDir, '.next/server/pages-manifest.json') + ) + + expect(manifest).toEqual({ + '/[name]/[comment]': 'pages/[name]/[comment].js', + '/[name]/comments': 'pages/[name]/comments.js', + '/[name]': 'pages/[name].js', + '/[name]/on-mount-redir': 'pages/[name]/on-mount-redir.html', + '/another': 'pages/another.html', + '/b/[123]': 'pages/b/[123].js', + '/blog/[name]/comment/[id]': 'pages/blog/[name]/comment/[id].js', + '/c/[alongparamnameshouldbeallowedeventhoughweird]': + 'pages/c/[alongparamnameshouldbeallowedeventhoughweird].js', + '/catchall-dash/[...hello-world]': + 'pages/catchall-dash/[...hello-world].html', + '/d/[id]': 'pages/d/[id].html', + '/dash/[hello-world]': 'pages/dash/[hello-world].html', + '/': 'pages/index.html', + '/index/[...slug]': 'pages/index/[...slug].html', + '/on-mount/[post]': 'pages/on-mount/[post].html', + '/p1/p2/all-ssg/[...rest]': 'pages/p1/p2/all-ssg/[...rest].js', + '/p1/p2/all-ssr/[...rest]': 'pages/p1/p2/all-ssr/[...rest].js', + '/p1/p2/nested-all-ssg/[...rest]': + 'pages/p1/p2/nested-all-ssg/[...rest].js', + '/p1/p2/predefined-ssg/[...rest]': + 'pages/p1/p2/predefined-ssg/[...rest].js', + '/_app': 'pages/_app.js', + '/_error': 'pages/_error.js', + '/_document': 'pages/_document.js', + '/404': 'pages/404.html', + }) + }) + } } } @@ -1354,7 +1399,7 @@ describe('Dynamic Routing', () => { }) afterAll(() => killApp(app)) - runTests(true) + runTests({ dev: true, serverless: false }) }) describe('production mode', () => { @@ -1369,7 +1414,7 @@ describe('Dynamic Routing', () => { }) afterAll(() => killApp(app)) - runTests() + runTests({ dev: false, serverless: false }) }) describe('serverless mode', () => { @@ -1389,6 +1434,6 @@ describe('Dynamic Routing', () => { await killApp(app) await fs.remove(nextConfig) }) - runTests() + runTests({ dev: false, serverless: true }) }) })