Skip to content

Commit 73f6b7d

Browse files
huozhiijjk
andauthored
Respect reexports from metadata API routes (#70508) (#70647)
backport #70508 Co-authored-by: JJ Kasper <jj@jjsweb.site>
1 parent e1da07e commit 73f6b7d

File tree

7 files changed

+141
-17
lines changed

7 files changed

+141
-17
lines changed

packages/next/src/build/webpack/loaders/next-metadata-route-loader.ts

Lines changed: 34 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,27 @@ function errorOnBadHandler(resourcePath: string) {
1414
`
1515
}
1616

17+
/* re-export the userland route configs */
18+
async function createReExportsCode(
19+
resourcePath: string,
20+
loaderContext: webpack.LoaderContext<any>
21+
) {
22+
const exportNames = await getLoaderModuleNamedExports(
23+
resourcePath,
24+
loaderContext
25+
)
26+
// Re-export configs but avoid conflicted exports
27+
const reExportNames = exportNames.filter(
28+
(name) => name !== 'default' && name !== 'generateSitemaps'
29+
)
30+
31+
return reExportNames.length > 0
32+
? `export { ${reExportNames.join(', ')} } from ${JSON.stringify(
33+
resourcePath
34+
)}\n`
35+
: ''
36+
}
37+
1738
const cacheHeader = {
1839
none: 'no-cache, no-store',
1940
longCache: 'public, immutable, no-transform, max-age=31536000',
@@ -83,7 +104,10 @@ export const dynamic = 'force-static'
83104
return code
84105
}
85106

86-
function getDynamicTextRouteCode(resourcePath: string) {
107+
async function getDynamicTextRouteCode(
108+
resourcePath: string,
109+
loaderContext: webpack.LoaderContext<any>
110+
) {
87111
return `\
88112
/* dynamic asset route */
89113
import { NextResponse } from 'next/server'
@@ -94,6 +118,7 @@ const contentType = ${JSON.stringify(getContentType(resourcePath))}
94118
const fileType = ${JSON.stringify(getFilenameAndExtension(resourcePath).name)}
95119
96120
${errorOnBadHandler(resourcePath)}
121+
${await createReExportsCode(resourcePath, loaderContext)}
97122
98123
export async function GET() {
99124
const data = await handler()
@@ -110,7 +135,10 @@ export async function GET() {
110135
}
111136

112137
// <metadata-image>/[id]/route.js
113-
function getDynamicImageRouteCode(resourcePath: string) {
138+
async function getDynamicImageRouteCode(
139+
resourcePath: string,
140+
loaderContext: webpack.LoaderContext<any>
141+
) {
114142
return `\
115143
/* dynamic image route */
116144
import { NextResponse } from 'next/server'
@@ -122,6 +150,7 @@ const handler = imageModule.default
122150
const generateImageMetadata = imageModule.generateImageMetadata
123151
124152
${errorOnBadHandler(resourcePath)}
153+
${await createReExportsCode(resourcePath, loaderContext)}
125154
126155
export async function GET(_, ctx) {
127156
const { __metadata_id__, ...params } = ctx.params || {}
@@ -160,10 +189,6 @@ async function getDynamicSiteMapRouteCode(
160189
resourcePath,
161190
loaderContext
162191
)
163-
// Re-export configs but avoid conflicted exports
164-
const reExportNames = exportNames.filter(
165-
(name) => name !== 'default' && name !== 'generateSitemaps'
166-
)
167192

168193
const hasGenerateSiteMaps = exportNames.includes('generateSitemaps')
169194
if (
@@ -197,15 +222,7 @@ const contentType = ${JSON.stringify(getContentType(resourcePath))}
197222
const fileType = ${JSON.stringify(getFilenameAndExtension(resourcePath).name)}
198223
199224
${errorOnBadHandler(resourcePath)}
200-
201-
${'' /* re-export the userland route configs */}
202-
${
203-
reExportNames.length > 0
204-
? `export { ${reExportNames.join(', ')} } from ${JSON.stringify(
205-
resourcePath
206-
)}\n`
207-
: ''
208-
}
225+
${await createReExportsCode(resourcePath, loaderContext)}
209226
210227
export async function GET(_, ctx) {
211228
const { __metadata_id__, ...params } = ctx.params || {}
@@ -266,11 +283,11 @@ const nextMetadataRouterLoader: webpack.LoaderDefinitionFunction<MetadataRouteLo
266283
let code = ''
267284
if (isDynamic === '1') {
268285
if (fileBaseName === 'robots' || fileBaseName === 'manifest') {
269-
code = getDynamicTextRouteCode(filePath)
286+
code = await getDynamicTextRouteCode(filePath, this)
270287
} else if (fileBaseName === 'sitemap') {
271288
code = await getDynamicSiteMapRouteCode(filePath, page, this)
272289
} else {
273-
code = getDynamicImageRouteCode(filePath)
290+
code = await getDynamicImageRouteCode(filePath, this)
274291
}
275292
} else {
276293
code = await getStaticAssetRouteCode(filePath, fileBaseName)
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
import type { MetadataRoute } from 'next'
2+
3+
export default function manifest(): MetadataRoute.Manifest {
4+
return {
5+
name: 'Next.js App',
6+
short_name: 'Next.js App',
7+
description: 'Next.js App',
8+
start_url: '/',
9+
display: 'standalone',
10+
background_color: '#fff',
11+
theme_color: '#fff',
12+
icons: [
13+
{
14+
src: '/favicon.ico',
15+
sizes: 'any',
16+
type: 'image/x-icon',
17+
},
18+
],
19+
}
20+
}
21+
22+
export const revalidate = 5
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
import { ImageResponse } from 'next/og'
2+
3+
/* without generateImageMetadata */
4+
export default function og() {
5+
return new ImageResponse(
6+
(
7+
<div
8+
style={{
9+
width: '100%',
10+
height: '100%',
11+
display: 'flex',
12+
alignItems: 'center',
13+
justifyContent: 'center',
14+
fontSize: 128,
15+
background: 'lavender',
16+
}}
17+
>
18+
Open Graph
19+
</div>
20+
)
21+
)
22+
}
23+
24+
export const revalidate = 5
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
import type { MetadataRoute } from 'next'
2+
3+
export default function robots(): MetadataRoute.Robots {
4+
return {
5+
rules: {
6+
userAgent: '*',
7+
allow: '/',
8+
disallow: '/private/',
9+
},
10+
sitemap: 'https://acme.com/sitemap.xml',
11+
}
12+
}
13+
14+
export const revalidate = 5
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
export default function sitemap() {
2+
return [
3+
{
4+
url: 'https://acme.com',
5+
lastModified: new Date(),
6+
changeFrequency: 'yearly',
7+
priority: 1,
8+
},
9+
]
10+
}
11+
12+
export const revalidate = 5
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
import { nextTestSetup } from 'e2e-utils'
2+
3+
describe('app-dir - metadata-revalidate', () => {
4+
const { next } = nextTestSetup({
5+
files: __dirname,
6+
})
7+
8+
it('should contain the routes in prerender manifest', async () => {
9+
const manifestContent = await next.readFile('.next/prerender-manifest.json')
10+
const prerenderManifest = JSON.parse(manifestContent)
11+
12+
expect(
13+
prerenderManifest.routes['/revalidate/og/opengraph-image']
14+
.initialRevalidateSeconds
15+
).toBe(5)
16+
expect(
17+
prerenderManifest.routes['/manifest.webmanifest'].initialRevalidateSeconds
18+
).toBe(5)
19+
expect(
20+
prerenderManifest.routes['/robots.txt'].initialRevalidateSeconds
21+
).toBe(5)
22+
expect(
23+
prerenderManifest.routes['/sitemap.xml'].initialRevalidateSeconds
24+
).toBe(5)
25+
})
26+
})

test/turbopack-build-tests-manifest.json

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15075,6 +15075,15 @@
1507515075
"flakey": [],
1507615076
"runtimeError": false
1507715077
},
15078+
"test/production/app-dir/metadata-revalidate/metadata-revalidate.test.ts": {
15079+
"passed": [],
15080+
"failed": [
15081+
"app-dir - metadata-revalidate should contain the routes in prerender manifest"
15082+
],
15083+
"pending": [],
15084+
"flakey": [],
15085+
"runtimeError": false
15086+
},
1507815087
"test/production/middleware-typescript/test/index.test.ts": {
1507915088
"passed": [],
1508015089
"failed": ["middleware-typescript should have built and started"],

0 commit comments

Comments
 (0)