Skip to content

Commit

Permalink
Optimize non-dynamic metadata routes to static in production build (#…
Browse files Browse the repository at this point in the history
…49109)

* For sitemap if they're not using dynamic routes generation `generateSitemaps`, should optimize them as static sitemap
* For icons and social images, if they're not using `generateImageMetadata`, should optimize them as static path

Closes NEXT-1071
Fixes #48991
  • Loading branch information
huozhi committed May 2, 2023
1 parent 9dc0c1e commit 9102771
Show file tree
Hide file tree
Showing 4 changed files with 105 additions and 20 deletions.
69 changes: 57 additions & 12 deletions packages/next/src/build/analysis/get-page-static-info.ts
Original file line number Diff line number Diff line change
Expand Up @@ -83,13 +83,23 @@ function checkExports(swcAST: any): {
ssg: boolean
runtime?: string
preferredRegion?: string | string[]
generateImageMetadata?: boolean
generateSitemaps?: boolean
} {
const exportsSet = new Set<string>([
'getStaticProps',
'getServerSideProps',
'generateImageMetadata',
'generateSitemaps',
])
if (Array.isArray(swcAST?.body)) {
try {
let runtime: string | undefined
let preferredRegion: string | string[] | undefined
let ssr: boolean = false
let ssg: boolean = false
let generateImageMetadata: boolean = false
let generateSitemaps: boolean = false

for (const node of swcAST.body) {
if (
Expand Down Expand Up @@ -122,22 +132,25 @@ function checkExports(swcAST: any): {
if (
node.type === 'ExportDeclaration' &&
node.declaration?.type === 'FunctionDeclaration' &&
['getStaticProps', 'getServerSideProps'].includes(
node.declaration.identifier?.value
)
exportsSet.has(node.declaration.identifier?.value)
) {
ssg = node.declaration.identifier.value === 'getStaticProps'
ssr = node.declaration.identifier.value === 'getServerSideProps'
const id = node.declaration.identifier.value
ssg = id === 'getStaticProps'
ssr = id === 'getServerSideProps'
generateImageMetadata = id === 'generateImageMetadata'
generateSitemaps = id === 'generateSitemaps'
}

if (
node.type === 'ExportDeclaration' &&
node.declaration?.type === 'VariableDeclaration'
) {
const id = node.declaration?.declarations[0]?.id.value
if (['getStaticProps', 'getServerSideProps'].includes(id)) {
if (exportsSet.has(id)) {
ssg = id === 'getStaticProps'
ssr = id === 'getServerSideProps'
generateImageMetadata = id === 'generateImageMetadata'
generateSitemaps = id === 'generateSitemaps'
}
}

Expand All @@ -149,18 +162,36 @@ function checkExports(swcAST: any): {
specifier.orig?.value
)

ssg = values.some((value: any) => ['getStaticProps'].includes(value))
ssr = values.some((value: any) =>
['getServerSideProps'].includes(value)
)
for (const value of values) {
if (!ssg && value === 'getStaticProps') ssg = true
if (!ssr && value === 'getServerSideProps') ssr = true
if (!generateImageMetadata && value === 'generateImageMetadata')
generateImageMetadata = true
if (!generateSitemaps && value === 'generateSitemaps')
generateSitemaps = true
}
}
}

return { ssr, ssg, runtime, preferredRegion }
return {
ssr,
ssg,
runtime,
preferredRegion,
generateImageMetadata,
generateSitemaps,
}
} catch (err) {}
}

return { ssg: false, ssr: false }
return {
ssg: false,
ssr: false,
runtime: undefined,
preferredRegion: undefined,
generateImageMetadata: false,
generateSitemaps: false,
}
}

async function tryToReadFile(filePath: string, shouldThrow: boolean) {
Expand Down Expand Up @@ -329,6 +360,20 @@ function warnAboutUnsupportedValue(
warnedUnsupportedValueMap.set(pageFilePath, true)
}

// Detect if metadata routes is a dynamic route, which containing
// generateImageMetadata or generateSitemaps as export
export async function isDynamicMetadataRoute(
pageFilePath: string
): Promise<boolean> {
const fileContent = (await tryToReadFile(pageFilePath, true)) || ''
if (!/generateImageMetadata|generateSitemaps/.test(fileContent)) return false

const swcAST = await parseModule(pageFilePath, fileContent)
const exportsInfo = checkExports(swcAST)

return !exportsInfo.generateImageMetadata || !exportsInfo.generateSitemaps
}

/**
* For a given pageFilePath and nextConfig, if the config supports it, this
* function will read the file and return the runtime that should be used.
Expand Down
28 changes: 26 additions & 2 deletions packages/next/src/build/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -90,8 +90,11 @@ import {
eventBuildCompleted,
} from '../telemetry/events'
import { Telemetry } from '../telemetry/storage'
import { getPageStaticInfo } from './analysis/get-page-static-info'
import { createPagesMapping } from './entries'
import {
isDynamicMetadataRoute,
getPageStaticInfo,
} from './analysis/get-page-static-info'
import { createPagesMapping, getPageFilePath } from './entries'
import { generateBuildId } from './generate-build-id'
import { isWriteable } from './is-writeable'
import * as Log from './output/log'
Expand Down Expand Up @@ -462,6 +465,27 @@ export default async function build(
pagesDir: pagesDir,
})
)

// If the metadata route doesn't contain generating dynamic exports,
// we can replace the dynamic catch-all route and use the static route instead.
for (const [pageKey, pagePath] of Object.entries(mappedAppPages)) {
if (pageKey.includes('[[...__metadata_id__]]')) {
const pageFilePath = getPageFilePath({
absolutePagePath: pagePath,
pagesDir,
appDir,
rootDir,
})

const isDynamic = await isDynamicMetadataRoute(pageFilePath)
if (!isDynamic) {
delete mappedAppPages[pageKey]
mappedAppPages[pageKey.replace('[[...__metadata_id__]]/', '')] =
pagePath
}
}
}

NextBuildContext.mappedAppPages = mappedAppPages
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -102,7 +102,7 @@ const handler = imageModule.default
const generateImageMetadata = imageModule.generateImageMetadata
export async function GET(_, ctx) {
const { __metadata_id__ = [], ...params } = ctx.params
const { __metadata_id__ = [], ...params } = ctx.params || {}
const targetId = __metadata_id__[0]
let id = undefined
const imageMetadata = generateImageMetadata ? await generateImageMetadata({ params }) : null
Expand All @@ -122,7 +122,7 @@ export async function GET(_, ctx) {
})
}
}
return handler({ params, id })
return handler({ params: ctx.params ? params : undefined, id })
}
`
}
Expand All @@ -141,7 +141,7 @@ const contentType = ${JSON.stringify(getContentType(resourcePath))}
const fileType = ${JSON.stringify(getFilenameAndExtension(resourcePath).name)}
export async function GET(_, ctx) {
const { __metadata_id__ = [], ...params } = ctx.params
const { __metadata_id__ = [], ...params } = ctx.params || {}
const targetId = __metadata_id__[0]
let id = undefined
const sitemaps = generateSitemaps ? await generateSitemaps() : null
Expand Down
22 changes: 19 additions & 3 deletions test/e2e/app-dir/metadata-dynamic-routes/index.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -420,15 +420,31 @@ createNextDescribe(
const edgeRoute = functionRoutes.find((route) =>
route.startsWith('/(group)/twitter-image-')
)
expect(edgeRoute).toMatch(
/\/\(group\)\/twitter-image-\w{6}\/\[\[\.\.\.__metadata_id__\]\]\/route/
expect(edgeRoute).toMatch(/\/\(group\)\/twitter-image-\w{6}\/route/)
})

it('should optimize routes without multiple generation API as static routes', async () => {
const appPathsManifest = JSON.parse(
await next.readFile('.next/server/app-paths-manifest.json')
)

expect(appPathsManifest).toMatchObject({
// static routes
'/twitter-image/route': 'app/twitter-image/route.js',
'/sitemap.xml/route': 'app/sitemap.xml/route.js',

// dynamic
'/(group)/dynamic/[size]/sitemap.xml/[[...__metadata_id__]]/route':
'app/(group)/dynamic/[size]/sitemap.xml/[[...__metadata_id__]]/route.js',
'/(group)/dynamic/[size]/apple-icon-48jo90/[[...__metadata_id__]]/route':
'app/(group)/dynamic/[size]/apple-icon-48jo90/[[...__metadata_id__]]/route.js',
})
})

it('should include default og font files in file trace', async () => {
const fileTrace = JSON.parse(
await next.readFile(
'.next/server/app/opengraph-image/[[...__metadata_id__]]/route.js.nft.json'
'.next/server/app/metadata-base/unset/opengraph-image2/[[...__metadata_id__]]/route.js.nft.json'
)
)

Expand Down

0 comments on commit 9102771

Please sign in to comment.