Skip to content

Commit

Permalink
Fix static metadata routes runtime when root layout is in edge runtime (
Browse files Browse the repository at this point in the history
vercel#50351)

Since the static metadata routes should always be static that can be
optimized, they should be marked as default runtime (or basically no
runtime). This PR change the runtime detection, if it's a static
metadata route file, its runtime will be erased.

So that it won't be picked up by `edge-app-route-loader` and then by
`next-image-loader` which emits the unexpected static file

Fixes NEXT-1238
  • Loading branch information
huozhi authored and hydRAnger committed Jun 12, 2023
1 parent 28f93c8 commit 94cfb6d
Show file tree
Hide file tree
Showing 16 changed files with 90 additions and 20 deletions.
8 changes: 8 additions & 0 deletions packages/next/src/build/entries.ts
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@ import { normalizeMetadataRoute } from '../lib/metadata/get-metadata-route'
import { fileExists } from '../lib/file-exists'
import { getRouteLoaderEntry } from './webpack/loaders/next-route-loader'
import { isInternalPathname } from '../lib/is-internal-pathname'
import { isStaticMetadataRouteFile } from '../lib/metadata/is-metadata-route'

export async function getStaticInfoIncludingLayouts({
isInsideAppDir,
Expand Down Expand Up @@ -128,6 +129,13 @@ export async function getStaticInfoIncludingLayouts({
if (pageStaticInfo.preferredRegion) {
staticInfo.preferredRegion = pageStaticInfo.preferredRegion
}

// if it's static metadata route, don't inherit runtime from layout
const relativePath = pageFilePath.replace(appDir, '')
if (isStaticMetadataRouteFile(relativePath)) {
delete staticInfo.runtime
delete staticInfo.preferredRegion
}
}
return staticInfo
}
Expand Down
4 changes: 2 additions & 2 deletions packages/next/src/build/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1970,7 +1970,7 @@ createServerHandler({
res.end('Internal Server Error')
}
})
if (
!Number.isNaN(keepAliveTimeout) &&
Number.isFinite(keepAliveTimeout) &&
Expand All @@ -1983,7 +1983,7 @@ createServerHandler({
console.error("Failed to start server", err)
process.exit(1)
}
console.log(
'Listening on port',
currentPort,
Expand Down
14 changes: 10 additions & 4 deletions packages/next/src/build/webpack-config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import {
RSC_ACTION_PROXY_ALIAS,
RSC_ACTION_CLIENT_WRAPPER_ALIAS,
RSC_ACTION_VALIDATE_ALIAS,
WEBPACK_RESOURCE_QUERIES,
} from '../lib/constants'
import { fileExists } from '../lib/file-exists'
import { CustomRoutes } from '../lib/load-custom-routes.js'
Expand Down Expand Up @@ -64,7 +65,6 @@ import { AppBuildManifestPlugin } from './webpack/plugins/app-build-manifest-plu
import { SubresourceIntegrityPlugin } from './webpack/plugins/subresource-integrity-plugin'
import { NextFontManifestPlugin } from './webpack/plugins/next-font-manifest-plugin'
import { getSupportedBrowsers } from './utils'
import { METADATA_RESOURCE_QUERY } from './webpack/loaders/metadata/discover'

type ExcludesFalse = <T>(x: T | false) => x is T
type ClientEntries = {
Expand Down Expand Up @@ -1942,7 +1942,9 @@ export default async function getBaseWebpackConfig(
// be in the SSR layer — here we convert the actual page request to
// the RSC layer via a webpack rule.
{
resourceQuery: /__edge_ssr_entry__/,
resourceQuery: new RegExp(
WEBPACK_RESOURCE_QUERIES.edgeSSREntry
),
layer: WEBPACK_LAYERS.server,
},
]
Expand Down Expand Up @@ -2039,7 +2041,9 @@ export default async function getBaseWebpackConfig(
},
{
test: codeCondition.test,
resourceQuery: /__edge_ssr_entry__/,
resourceQuery: new RegExp(
WEBPACK_RESOURCE_QUERIES.edgeSSREntry
),
use: swcLoaderForServerLayer,
},
{
Expand Down Expand Up @@ -2087,7 +2091,9 @@ export default async function getBaseWebpackConfig(
loader: 'next-image-loader',
issuer: { not: regexLikeCss },
dependency: { not: ['url'] },
resourceQuery: { not: [METADATA_RESOURCE_QUERY] },
resourceQuery: {
not: [new RegExp(WEBPACK_RESOURCE_QUERIES.metadata)],
},
options: {
isServer: isNodeServer || isEdgeServer,
isDev: dev,
Expand Down
5 changes: 2 additions & 3 deletions packages/next/src/build/webpack/loaders/metadata/discover.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,10 @@ import type {
import path from 'path'
import { stringify } from 'querystring'
import { STATIC_METADATA_IMAGES } from '../../../../lib/metadata/is-metadata-route'
import { WEBPACK_RESOURCE_QUERIES } from '../../../../lib/constants'

const METADATA_TYPE = 'metadata'

export const METADATA_RESOURCE_QUERY = '?__next_metadata'

// Produce all compositions with filename (icon, apple-icon, etc.) with extensions (png, jpg, etc.)
async function enumMetadataFiles(
dir: string,
Expand Down Expand Up @@ -129,7 +128,7 @@ export async function createStaticMetadataFromRoute(
basePath,
pageExtensions,
}
)}!${filepath}${METADATA_RESOURCE_QUERY}`
)}!${filepath}?${WEBPACK_RESOURCE_QUERIES.metadata}`

const imageModule = `(async (props) => (await import(/* webpackMode: "eager" */ ${JSON.stringify(
imageModuleImportSource
Expand Down
5 changes: 2 additions & 3 deletions packages/next/src/build/webpack/loaders/next-app-loader.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,10 @@ import { NODE_RESOLVE_OPTIONS } from '../../webpack-config'
import { getModuleBuildInfo } from './get-module-build-info'
import { verifyRootLayout } from '../../../lib/verifyRootLayout'
import * as Log from '../../output/log'
import { APP_DIR_ALIAS } from '../../../lib/constants'
import { APP_DIR_ALIAS, WEBPACK_RESOURCE_QUERIES } from '../../../lib/constants'
import {
createMetadataExportsCode,
createStaticMetadataFromRoute,
METADATA_RESOURCE_QUERY,
} from './metadata/discover'
import { promises as fs } from 'fs'
import { isAppRouteRoute } from '../../../lib/is-app-route-route'
Expand Down Expand Up @@ -102,7 +101,7 @@ async function createAppRouteCode({
resolvedPagePath = `next-metadata-route-loader?${stringify({
page,
pageExtensions,
})}!${resolvedPagePath + METADATA_RESOURCE_QUERY}`
})}!${resolvedPagePath + '?' + WEBPACK_RESOURCE_QUERIES.metadata}`
}

// References the route handler file to load found in `./routes/${kind}.ts`.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { getModuleBuildInfo } from '../get-module-build-info'
import { stringifyRequest } from '../../stringify-request'
import { NextConfig } from '../../../../server/config-shared'
import { webpack } from 'next/dist/compiled/webpack/webpack'
import { WEBPACK_RESOURCE_QUERIES } from '../../../../lib/constants'

export type EdgeAppRouteLoaderQuery = {
absolutePagePath: string
Expand Down Expand Up @@ -42,7 +43,9 @@ const EdgeAppRouteLoader: webpack.LoaderDefinitionFunction<EdgeAppRouteLoaderQue
const modulePath = `${appDirLoader}${stringifiedPagePath.substring(
1,
stringifiedPagePath.length - 1
)}?__edge_ssr_entry__`
)}?${WEBPACK_RESOURCE_QUERIES.edgeSSREntry}&${
WEBPACK_RESOURCE_QUERIES.metadata
}`

return `
import { EdgeRouteModuleWrapper } from 'next/dist/esm/server/web/edge-route-module-wrapper'
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import type webpack from 'webpack'
import { getModuleBuildInfo } from '../get-module-build-info'
import { WEBPACK_RESOURCE_QUERIES } from '../../../../lib/constants'
import { stringifyRequest } from '../../stringify-request'

export type EdgeSSRLoaderQuery = {
Expand Down Expand Up @@ -91,7 +92,7 @@ const edgeSSRLoader: webpack.LoaderDefinitionFunction<EdgeSSRLoaderQuery> =
const pageModPath = `${appDirLoader}${stringifiedPagePath.substring(
1,
stringifiedPagePath.length - 1
)}${isAppDir ? '?__edge_ssr_entry__' : ''}`
)}${isAppDir ? `?${WEBPACK_RESOURCE_QUERIES.edgeSSREntry}` : ''}`

const transformed = `
import { adapter, enhanceGlobals } from 'next/dist/esm/server/web/adapter'
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import type webpack from 'webpack'
import fs from 'fs'
import path from 'path'
import { METADATA_RESOURCE_QUERY } from './metadata/discover'
import { imageExtMimeTypeMap } from '../../../lib/mime-type'
import { WEBPACK_RESOURCE_QUERIES } from '../../../lib/constants'

const cacheHeader = {
none: 'no-cache, no-store',
Expand Down Expand Up @@ -51,7 +51,7 @@ const contentType = ${JSON.stringify(getContentType(resourcePath))}
const buffer = Buffer.from(${JSON.stringify(
(
await fs.promises.readFile(
resourcePath.replace(METADATA_RESOURCE_QUERY, '')
resourcePath.replace('?' + WEBPACK_RESOURCE_QUERIES.metadata, '')
)
).toString('base64')
)}, 'base64'
Expand Down
5 changes: 5 additions & 0 deletions packages/next/src/lib/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -96,3 +96,8 @@ export const WEBPACK_LAYERS = {
edgeAsset: 'edge-asset',
appClient: 'app-client',
}

export const WEBPACK_RESOURCE_QUERIES = {
edgeSSREntry: '__next_edge_ssr_entry__',
metadata: '__next_metadata__',
}
4 changes: 2 additions & 2 deletions packages/next/src/lib/metadata/get-metadata-route.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { isMetadataRoute, isMetadataRouteFile } from './is-metadata-route'
import { isMetadataRoute, isStaticMetadataRouteFile } from './is-metadata-route'
import path from '../../shared/lib/isomorphic/path'
import { interpolateDynamicPath } from '../../server/server-utils'
import { getNamedRouteRegex } from '../../shared/lib/router/utils/route-regex'
Expand Down Expand Up @@ -74,7 +74,7 @@ export function normalizeMetadataRoute(page: string) {
// Support both /<metadata-route.ext> and custom routes /<metadata-route>/route.ts.
// If it's a metadata file route, we need to append /[id]/route to the page.
if (!route.endsWith('/route')) {
const isStaticMetadataFile = isMetadataRouteFile(page, [], true)
const isStaticMetadataFile = isStaticMetadataRouteFile(page)
const { dir, name: baseName, ext } = path.parse(route)

const isStaticRoute =
Expand Down
11 changes: 10 additions & 1 deletion packages/next/src/lib/metadata/is-metadata-route.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import { normalizePathSep } from '../../shared/lib/page-path/normalize-path-sep'

export const STATIC_METADATA_IMAGES = {
icon: {
filename: 'icon',
Expand Down Expand Up @@ -102,7 +104,14 @@ export function isMetadataRouteFile(
),
]

return metadataRouteFilesRegex.some((r) => r.test(appDirRelativePath))
const normalizedAppDirRelativePath = normalizePathSep(appDirRelativePath)
return metadataRouteFilesRegex.some((r) =>
r.test(normalizedAppDirRelativePath)
)
}

export function isStaticMetadataRouteFile(appDirRelativePath: string) {
return isMetadataRouteFile(appDirRelativePath, [], true)
}

/*
Expand Down
Binary file not shown.
9 changes: 9 additions & 0 deletions test/e2e/app-dir/app-edge-root-layout/app/layout.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
export default function layout({ children }) {
return (
<html>
<body>{children}</body>
</html>
)
}

export const runtime = 'experimental-edge'
3 changes: 3 additions & 0 deletions test/e2e/app-dir/app-edge-root-layout/app/page.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export default function page() {
return <div>hello edge</div>
}
29 changes: 29 additions & 0 deletions test/e2e/app-dir/app-edge-root-layout/index.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import { createNextDescribe } from 'e2e-utils'

createNextDescribe(
'app-dir edge runtime root layout',
{
files: __dirname,
skipDeployment: true,
},
({ next, isNextStart }) => {
it('should not emit metadata files into bad paths', async () => {
await next.fetch('/favicon.ico')
// issue: If metadata files are not filter out properly with image-loader,
// an incorrect static/media folder will be generated

// Check that the static folder is not generated
const incorrectGeneratedStaticFolder = await next.hasFile('static')
expect(incorrectGeneratedStaticFolder).toBe(false)
})

if (isNextStart) {
it('should mark static contain metadata routes as edge functions', async () => {
const middlewareManifest = await next.readFile(
'.next/server/middleware-manifest.json'
)
expect(middlewareManifest).not.toContain('favicon')
})
}
}
)
1 change: 0 additions & 1 deletion test/e2e/app-dir/app-edge/next.config.js

This file was deleted.

0 comments on commit 94cfb6d

Please sign in to comment.