Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix static metadata routes runtime when root layout is in edge runtime #50351

Merged
merged 4 commits into from
May 25, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
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
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.