diff --git a/packages/next/src/build/webpack-config.ts b/packages/next/src/build/webpack-config.ts index fd9e9a76380c..fb6e5b1cfd12 100644 --- a/packages/next/src/build/webpack-config.ts +++ b/packages/next/src/build/webpack-config.ts @@ -1077,6 +1077,9 @@ export default async function getBaseWebpackConfig( ...nodePathList, // Support for NODE_PATH environment variable ], alias: { + // Alias 3rd party @vercel/og package to vendored og image package to reduce bundle size + '@vercel/og': 'next/dist/server/web/spec-extension/image-response', + // Alias next/dist imports to next/dist/esm assets, // let this alias hit before `next` alias. ...(isEdgeServer @@ -1088,25 +1091,27 @@ export default async function getBaseWebpackConfig( 'next/dist/server': 'next/dist/esm/server', // Alias the usage of next public APIs - [require.resolve('next/dist/client/link')]: + [`${NEXT_PROJECT_ROOT}/server`]: + 'next/dist/esm/server/web/exports/index', + [`${NEXT_PROJECT_ROOT}/dist/client/link`]: 'next/dist/esm/client/link', - [require.resolve('next/dist/client/image')]: + [`${NEXT_PROJECT_ROOT}/dist/client/image`]: 'next/dist/esm/client/image', - [require.resolve('next/dist/client/script')]: + [`${NEXT_PROJECT_ROOT}/dist/client/script`]: 'next/dist/esm/client/script', - [require.resolve('next/dist/client/router')]: + [`${NEXT_PROJECT_ROOT}/dist/client/router`]: 'next/dist/esm/client/router', - [require.resolve('next/dist/shared/lib/head')]: + [`${NEXT_PROJECT_ROOT}/dist/shared/lib/head`]: 'next/dist/esm/shared/lib/head', - [require.resolve('next/dist/shared/lib/dynamic')]: + [`${NEXT_PROJECT_ROOT}/dist/shared/lib/dynamic`]: 'next/dist/esm/shared/lib/dynamic', - [require.resolve('next/dist/pages/_document')]: + [`${NEXT_PROJECT_ROOT}/dist/pages/_document`]: 'next/dist/esm/pages/_document', - [require.resolve('next/dist/pages/_app')]: + [`${NEXT_PROJECT_ROOT}/dist/pages/_app`]: 'next/dist/esm/pages/_app', - [require.resolve('next/dist/client/components/navigation')]: + [`${NEXT_PROJECT_ROOT}/dist/client/components/navigation`]: 'next/dist/esm/client/components/navigation', - [require.resolve('next/dist/client/components/headers')]: + [`${NEXT_PROJECT_ROOT}/dist/client/components/headers`]: 'next/dist/esm/client/components/headers', } : undefined), @@ -1433,8 +1438,8 @@ export default async function getBaseWebpackConfig( resolveResult.res = require.resolve(request) } - // Don't bundle @vercel/og nodejs bundle for nodejs runtime - // TODO-APP: bundle route.js with different layer that externals common node_module deps + // Don't bundle @vercel/og nodejs bundle for nodejs runtime. + // TODO-APP: bundle route.js with different layer that externals common node_module deps. if ( layer === WEBPACK_LAYERS.server && request === 'next/dist/compiled/@vercel/og/index.node.js' diff --git a/packages/next/src/build/webpack/plugins/middleware-plugin.ts b/packages/next/src/build/webpack/plugins/middleware-plugin.ts index 709a4626f0fd..00cb6b889e25 100644 --- a/packages/next/src/build/webpack/plugins/middleware-plugin.ts +++ b/packages/next/src/build/webpack/plugins/middleware-plugin.ts @@ -570,7 +570,7 @@ function getExtractMetadata(params: { const resource = module.resource const hasOGImageGeneration = resource && - /[\\/]node_modules[\\/]@vercel[\\/]og[\\/]dist[\\/]index\.(edge|node)\.js$|[\\/]next[\\/]dist[\\/]server[\\/]web[\\/]spec-extension[\\/]image-response\.js$/.test( + /[\\/]node_modules[\\/]@vercel[\\/]og[\\/]dist[\\/]index\.(edge|node)\.js$|[\\/]next[\\/]dist[\\/](esm[\\/])?server[\\/]web[\\/]spec-extension[\\/]image-response\.js$/.test( resource ) diff --git a/packages/next/src/lib/server-external-packages.json b/packages/next/src/lib/server-external-packages.json index 9fed54504e6c..7ed606365109 100644 --- a/packages/next/src/lib/server-external-packages.json +++ b/packages/next/src/lib/server-external-packages.json @@ -7,7 +7,6 @@ "@sentry/nextjs", "@sentry/node", "@swc/core", - "@vercel/og", "argon2", "autoprefixer", "aws-crt", diff --git a/packages/next/src/server/web/exports/index.ts b/packages/next/src/server/web/exports/index.ts new file mode 100644 index 000000000000..d461efb78dc1 --- /dev/null +++ b/packages/next/src/server/web/exports/index.ts @@ -0,0 +1,8 @@ +// Alias index file of next/server for edge runtime for tree-shaking purpose + +export { default as ImageResponse } from './image-response' +export { default as NextRequest } from './next-request' +export { default as NextResponse } from './next-response' +export { default as userAgent } from './user-agent' +export { default as userAgentFromString } from './user-agent-from-string' +export { default as URLPattern } from './url-pattern' diff --git a/packages/next/src/server/web/spec-extension/image-response.ts b/packages/next/src/server/web/spec-extension/image-response.ts index fdbb4e974ee9..bccd7401a255 100644 --- a/packages/next/src/server/web/spec-extension/image-response.ts +++ b/packages/next/src/server/web/spec-extension/image-response.ts @@ -1,4 +1,5 @@ export class ImageResponse { + public static displayName = 'NextImageResponse' constructor( ...args: ConstructorParameters< typeof import('next/dist/compiled/@vercel/og').ImageResponse diff --git a/test/e2e/app-dir/metadata-dynamic-routes/app/twitter-image.tsx b/test/e2e/app-dir/metadata-dynamic-routes/app/twitter-image.tsx index ec3e38f7c3e5..aaa0f49e5b2a 100644 --- a/test/e2e/app-dir/metadata-dynamic-routes/app/twitter-image.tsx +++ b/test/e2e/app-dir/metadata-dynamic-routes/app/twitter-image.tsx @@ -1,4 +1,14 @@ import { ImageResponse } from 'next/server' +// @ts-ignore +import { ImageResponse as ImageResponse2 } from '@vercel/og' + +// Node.js: Using @vercel/og external package, and should be aliased to "next/server" ImageResponse +if (ImageResponse.displayName !== 'NextImageResponse') + throw new Error('ImageResponse mismatch: ' + ImageResponse.displayName) +// @ts-ignore +if (ImageResponse2.displayName !== 'NextImageResponse') + // @ts-ignore + throw new Error('ImageResponse mismatch: ' + ImageResponse2.displayName) export const alt = 'Twitter' export const size = { width: 1600, height: 900 } diff --git a/test/e2e/app-dir/metadata-dynamic-routes/app/twitter-image2.tsx b/test/e2e/app-dir/metadata-dynamic-routes/app/twitter-image2.tsx new file mode 100644 index 000000000000..6fac563558bb --- /dev/null +++ b/test/e2e/app-dir/metadata-dynamic-routes/app/twitter-image2.tsx @@ -0,0 +1,37 @@ +import { ImageResponse } from 'next/server' +// @ts-ignore +import { ImageResponse as ImageResponse2 } from '@vercel/og' + +// Edge: Using @vercel/og external package, and should be aliased to "next/server" ImageResponse +if (ImageResponse.displayName !== 'NextImageResponse') + throw new Error('ImageResponse mismatch: ' + ImageResponse.displayName) +// @ts-ignore +if (ImageResponse2.displayName !== 'NextImageResponse') + // @ts-ignore + throw new Error('ImageResponse2 mismatch: ' + ImageResponse2.displayName) + +export const alt = 'Twitter' +export const size = { width: 1600, height: 900 } + +export default function twitter() { + return new ImageResponse( + ( +
+ Twitter Image +
+ ), + size + ) +} + +export const runtime = 'edge' diff --git a/test/e2e/app-dir/metadata-dynamic-routes/index.test.ts b/test/e2e/app-dir/metadata-dynamic-routes/index.test.ts index 71224c59c4f2..26d7a248e73c 100644 --- a/test/e2e/app-dir/metadata-dynamic-routes/index.test.ts +++ b/test/e2e/app-dir/metadata-dynamic-routes/index.test.ts @@ -14,6 +14,9 @@ createNextDescribe( 'app dir - metadata dynamic routes', { files: __dirname, + dependencies: { + '@vercel/og': 'latest', + }, }, ({ next, isNextDev, isNextStart, isNextDeploy }) => { describe('text routes', () => { @@ -101,12 +104,20 @@ createNextDescribe( }) it('should render og image with twitter-image dynamic routes', async () => { - const res = await next.fetch('/twitter-image') + // nodejs runtime + let res = await next.fetch('/twitter-image') expect(res.headers.get('content-type')).toBe('image/png') expect(res.headers.get('cache-control')).toBe( isNextDev ? CACHE_HEADERS.NONE : CACHE_HEADERS.LONG ) + + // edge runtime + res = await next.fetch('/twitter-image2') + expect(res.headers.get('content-type')).toBe('image/png') + expect(res.headers.get('cache-control')).toBe( + isNextDev ? CACHE_HEADERS.NONE : CACHE_HEADERS.LONG + ) }) it('should support generate multi images with generateImageMetadata', async () => { diff --git a/test/e2e/app-dir/metadata-dynamic-routes/next.config.js b/test/e2e/app-dir/metadata-dynamic-routes/next.config.js index 4ba52ba2c8df..dc0c3a93b03e 100644 --- a/test/e2e/app-dir/metadata-dynamic-routes/next.config.js +++ b/test/e2e/app-dir/metadata-dynamic-routes/next.config.js @@ -1 +1,9 @@ module.exports = {} + +// For development: analyze the bundled chunks for stats app +if (process.env.ANALYZE) { + const withBundleAnalyzer = require('@next/bundle-analyzer')({ + enabled: true, + }) + module.exports = withBundleAnalyzer(module.exports) +}