From 1e332532c7e0036de2e09802b83eb7d692b2d654 Mon Sep 17 00:00:00 2001 From: Zack Tanner Date: Thu, 14 Sep 2023 18:11:03 -0700 Subject: [PATCH] fix manifest load errors when using assetPrefix (#55416) ### What? `_devPagesManifest.json` and `_devMiddlewareManifest.json` will fail to load when using an asset prefix. In conjunction with i18n, this causes the app to get caught in an infinite load loop. ### Why? We're expecting these paths to be exact matches but when there's an assetPrefix specified, they won't be matched. ### How? This copies similar behavior to how we handle [`webpack-hmr`](https://github.com/vercel/next.js/blob/2e2211d27b3f0ff1cff5553453df4cf391996163/packages/next/src/server/lib/router-server.ts#L681) by doing a partial match on the URL when serving it Closes NEXT-1618 Fixes #55389 --- packages/next/src/client/page-loader.ts | 8 ++++-- .../src/server/lib/router-utils/setup-dev.ts | 4 +-- test/development/basic/asset-prefix.test.ts | 28 +++++++++++++++++++ .../basic/asset-prefix/next.config.js | 20 +++++++++++++ .../basic/asset-prefix/pages/index.js | 5 ++++ 5 files changed, 61 insertions(+), 4 deletions(-) create mode 100644 test/development/basic/asset-prefix.test.ts create mode 100644 test/development/basic/asset-prefix/next.config.js create mode 100644 test/development/basic/asset-prefix/pages/index.js diff --git a/packages/next/src/client/page-loader.ts b/packages/next/src/client/page-loader.ts index 06cc6bcde6164..76ecb8fb0439d 100644 --- a/packages/next/src/client/page-loader.ts +++ b/packages/next/src/client/page-loader.ts @@ -9,6 +9,10 @@ import { isDynamicRoute } from '../shared/lib/router/utils/is-dynamic' import { parseRelativeUrl } from '../shared/lib/router/utils/parse-relative-url' import { removeTrailingSlash } from '../shared/lib/router/utils/remove-trailing-slash' import { createRouteLoader, getClientBuildManifest } from './route-loader' +import { + DEV_CLIENT_PAGES_MANIFEST, + DEV_MIDDLEWARE_MANIFEST, +} from '../shared/lib/constants' declare global { interface Window { @@ -60,7 +64,7 @@ export default class PageLoader { return window.__DEV_PAGES_MANIFEST.pages } else { this.promisedDevPagesManifest ||= fetch( - `${this.assetPrefix}/_next/static/development/_devPagesManifest.json` + `${this.assetPrefix}/_next/static/development/${DEV_CLIENT_PAGES_MANIFEST}` ) .then((res) => res.json()) .then((manifest: { pages: string[] }) => { @@ -94,7 +98,7 @@ export default class PageLoader { // TODO: Decide what should happen when fetching fails instead of asserting // @ts-ignore this.promisedMiddlewareMatchers = fetch( - `${this.assetPrefix}/_next/static/${this.buildId}/_devMiddlewareManifest.json` + `${this.assetPrefix}/_next/static/${this.buildId}/${DEV_MIDDLEWARE_MANIFEST}` ) .then((res) => res.json()) .then((matchers: MiddlewareMatcher[]) => { diff --git a/packages/next/src/server/lib/router-utils/setup-dev.ts b/packages/next/src/server/lib/router-utils/setup-dev.ts index cd471284ffd81..88f63f8265091 100644 --- a/packages/next/src/server/lib/router-utils/setup-dev.ts +++ b/packages/next/src/server/lib/router-utils/setup-dev.ts @@ -1919,7 +1919,7 @@ async function startWatcher(opts: SetupOpts) { async function requestHandler(req: IncomingMessage, res: ServerResponse) { const parsedUrl = url.parse(req.url || '/') - if (parsedUrl.pathname === clientPagesManifestPath) { + if (parsedUrl.pathname?.includes(clientPagesManifestPath)) { res.statusCode = 200 res.setHeader('Content-Type', 'application/json; charset=utf-8') res.end( @@ -1932,7 +1932,7 @@ async function startWatcher(opts: SetupOpts) { return { finished: true } } - if (parsedUrl.pathname === devMiddlewareManifestPath) { + if (parsedUrl.pathname?.includes(devMiddlewareManifestPath)) { res.statusCode = 200 res.setHeader('Content-Type', 'application/json; charset=utf-8') res.end(JSON.stringify(serverFields.middleware?.matchers || [])) diff --git a/test/development/basic/asset-prefix.test.ts b/test/development/basic/asset-prefix.test.ts new file mode 100644 index 0000000000000..91e6f90adee64 --- /dev/null +++ b/test/development/basic/asset-prefix.test.ts @@ -0,0 +1,28 @@ +import { join } from 'path' +import { createNextDescribe } from 'e2e-utils' +import { check } from 'next-test-utils' + +createNextDescribe( + 'asset-prefix', + { + files: join(__dirname, 'asset-prefix'), + }, + ({ next }) => { + it('should load the app properly without reloading', async () => { + const browser = await next.browser('/') + await browser.eval(`window.__v = 1`) + + expect(await browser.elementByCss('div').text()).toBe('Hello World') + + await check(async () => { + const logs = await browser.log() + const hasError = logs.some((log) => + log.message.includes('Failed to fetch') + ) + return hasError ? 'error' : 'success' + }, 'success') + + expect(await browser.eval(`window.__v`)).toBe(1) + }) + } +) diff --git a/test/development/basic/asset-prefix/next.config.js b/test/development/basic/asset-prefix/next.config.js new file mode 100644 index 0000000000000..cbce94fd47847 --- /dev/null +++ b/test/development/basic/asset-prefix/next.config.js @@ -0,0 +1,20 @@ +const ASSET_PREFIX = 'asset-prefix' + +module.exports = { + assetPrefix: ASSET_PREFIX, + i18n: { + locales: ['en-US'], + defaultLocale: 'en-US', + }, + async rewrites() { + return { + beforeFiles: [ + { + source: `/:locale/${ASSET_PREFIX}/_next/:path*`, + destination: '/_next/:path*', + locale: false, + }, + ], + } + }, +} diff --git a/test/development/basic/asset-prefix/pages/index.js b/test/development/basic/asset-prefix/pages/index.js new file mode 100644 index 0000000000000..135367d1f85f3 --- /dev/null +++ b/test/development/basic/asset-prefix/pages/index.js @@ -0,0 +1,5 @@ +import React from 'react' + +export default function Page() { + return
Hello World
+}