Skip to content

Commit 269fc73

Browse files
committed
Respect reexports from metadata API routes
1 parent 3ead2d1 commit 269fc73

File tree

5 files changed

+126
-17
lines changed

5 files changed

+126
-17
lines changed

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

+35-17
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,28 @@ 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+
return ''
23+
const exportNames = await getLoaderModuleNamedExports(
24+
resourcePath,
25+
loaderContext
26+
)
27+
// Re-export configs but avoid conflicted exports
28+
const reExportNames = exportNames.filter(
29+
(name) => name !== 'default' && name !== 'generateSitemaps'
30+
)
31+
32+
return reExportNames.length > 0
33+
? `export { ${reExportNames.join(', ')} } from ${JSON.stringify(
34+
resourcePath
35+
)}\n`
36+
: ''
37+
}
38+
1739
const cacheHeader = {
1840
none: 'no-cache, no-store',
1941
longCache: 'public, immutable, no-transform, max-age=31536000',
@@ -85,7 +107,10 @@ export const dynamic = 'force-static'
85107
return code
86108
}
87109

88-
function getDynamicTextRouteCode(resourcePath: string) {
110+
async function getDynamicTextRouteCode(
111+
resourcePath: string,
112+
loaderContext: webpack.LoaderContext<any>
113+
) {
89114
return `\
90115
/* dynamic asset route */
91116
import { NextResponse } from 'next/server'
@@ -96,6 +121,7 @@ const contentType = ${JSON.stringify(getContentType(resourcePath))}
96121
const fileType = ${JSON.stringify(getFilenameAndExtension(resourcePath).name)}
97122
98123
${errorOnBadHandler(resourcePath)}
124+
${await createReExportsCode(resourcePath, loaderContext)}
99125
100126
export async function GET() {
101127
const data = await handler()
@@ -112,7 +138,10 @@ export async function GET() {
112138
}
113139

114140
// <metadata-image>/[id]/route.js
115-
function getDynamicImageRouteCode(resourcePath: string) {
141+
async function getDynamicImageRouteCode(
142+
resourcePath: string,
143+
loaderContext: webpack.LoaderContext<any>
144+
) {
116145
return `\
117146
/* dynamic image route */
118147
import { NextResponse } from 'next/server'
@@ -124,6 +153,7 @@ const handler = imageModule.default
124153
const generateImageMetadata = imageModule.generateImageMetadata
125154
126155
${errorOnBadHandler(resourcePath)}
156+
${await createReExportsCode(resourcePath, loaderContext)}
127157
128158
export async function GET(_, ctx) {
129159
const { __metadata_id__, ...params } = ctx.params || {}
@@ -162,10 +192,6 @@ async function getDynamicSitemapRouteCode(
162192
resourcePath,
163193
loaderContext
164194
)
165-
// Re-export configs but avoid conflicted exports
166-
const reExportNames = exportNames.filter(
167-
(name) => name !== 'default' && name !== 'generateSitemaps'
168-
)
169195

170196
const hasGenerateSitemaps = exportNames.includes('generateSitemaps')
171197

@@ -195,15 +221,7 @@ const contentType = ${JSON.stringify(getContentType(resourcePath))}
195221
const fileType = ${JSON.stringify(getFilenameAndExtension(resourcePath).name)}
196222
197223
${errorOnBadHandler(resourcePath)}
198-
199-
${'' /* re-export the userland route configs */}
200-
${
201-
reExportNames.length > 0
202-
? `export { ${reExportNames.join(', ')} } from ${JSON.stringify(
203-
resourcePath
204-
)}\n`
205-
: ''
206-
}
224+
${await createReExportsCode(resourcePath, loaderContext)}
207225
208226
export async function GET(_, ctx) {
209227
const { __metadata_id__: id, ...params } = ctx.params || {}
@@ -253,11 +271,11 @@ const nextMetadataRouterLoader: webpack.LoaderDefinitionFunction<MetadataRouteLo
253271
let code = ''
254272
if (isDynamicRouteExtension === '1') {
255273
if (fileBaseName === 'robots' || fileBaseName === 'manifest') {
256-
code = getDynamicTextRouteCode(filePath)
274+
code = await getDynamicTextRouteCode(filePath, this)
257275
} else if (fileBaseName === 'sitemap') {
258276
code = await getDynamicSitemapRouteCode(filePath, this)
259277
} else {
260-
code = getDynamicImageRouteCode(filePath)
278+
code = await getDynamicImageRouteCode(filePath, this)
261279
}
262280
} else {
263281
code = await getStaticAssetRouteCode(filePath, fileBaseName)
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
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
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
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
import { nextTestSetup } from 'e2e-utils'
2+
3+
describe('app-dir - metadata-revalidate', () => {
4+
const { next, isNextStart } = nextTestSetup({
5+
files: __dirname,
6+
})
7+
8+
it('should contain the routes in prerender manifest', async () => {
9+
if (isNextStart) {
10+
const manifestContent = await next.readFile(
11+
'.next/prerender-manifest.json'
12+
)
13+
const prerenderManifest = JSON.parse(manifestContent)
14+
15+
expect(
16+
prerenderManifest.routes['/revalidate/og/opengraph-image']
17+
.initialRevalidateSeconds
18+
).toBe(5)
19+
expect(
20+
prerenderManifest.routes['/manifest.webmanifest']
21+
.initialRevalidateSeconds
22+
).toBe(5)
23+
expect(
24+
prerenderManifest.routes['/robots.txt'].initialRevalidateSeconds
25+
).toBe(5)
26+
expect(
27+
prerenderManifest.routes['/sitemap.xml'].initialRevalidateSeconds
28+
).toBe(5)
29+
}
30+
})
31+
})

0 commit comments

Comments
 (0)