@@ -10,7 +10,7 @@ import {
1010writeFile ,
1111} from 'node:fs/promises'
1212import { createRequire } from 'node:module'
13- import { dirname , join , resolve , sep } from 'node:path'
13+ import { dirname , join , relative , sep } from 'node:path'
1414import { join as posixJoin , sep as posixSep } from 'node:path/posix'
1515
1616import { trace } from '@opentelemetry/api'
@@ -116,36 +116,53 @@ export const copyNextServerCode = async (ctx: PluginContext): Promise<void> => {
116116} ,
117117)
118118
119- await Promise . all (
120- paths . map ( async ( path :string ) => {
121- const srcPath = join ( srcDir , path )
122- const destPath = join ( destDir , path )
123-
124- // If this is the middleware manifest file, replace it with an empty
125- // manifest to avoid running middleware again in the server handler.
126- if ( path === 'server/middleware-manifest.json' ) {
127- try {
128- await replaceMiddlewareManifest ( srcPath , destPath )
129- } catch ( error ) {
130- throw new Error ( 'Could not patch middleware manifest file' , { cause :error } )
131- }
119+ const promises = paths . map ( async ( path :string ) => {
120+ const srcPath = join ( srcDir , path )
121+ const destPath = join ( destDir , path )
132122
133- return
123+ // If this is the middleware manifest file, replace it with an empty
124+ // manifest to avoid running middleware again in the server handler.
125+ if ( path === 'server/middleware-manifest.json' ) {
126+ try {
127+ await replaceMiddlewareManifest ( srcPath , destPath )
128+ } catch ( error ) {
129+ throw new Error ( 'Could not patch middleware manifest file' , { cause :error } )
134130}
135131
136- if ( path === 'server/functions-config-manifest.json' ) {
137- try {
138- await replaceFunctionsConfigManifest ( srcPath , destPath )
139- } catch ( error ) {
140- throw new Error ( 'Could not patch functions config manifest file' , { cause :error } )
141- }
132+ return
133+ }
142134
143- return
135+ if ( path === 'server/functions-config-manifest.json' ) {
136+ try {
137+ await replaceFunctionsConfigManifest ( srcPath , destPath )
138+ } catch ( error ) {
139+ throw new Error ( 'Could not patch functions config manifest file' , { cause :error } )
144140}
145141
146- await cp ( srcPath , destPath , { recursive :true , force :true } )
147- } ) ,
148- )
142+ return
143+ }
144+
145+ await cp ( srcPath , destPath , { recursive :true , force :true } )
146+ } )
147+
148+ // this is different node_modules than ones handled by `copyNextDependencies`
149+ // this is under the standalone/.next folder (not standalone/node_modules or standalone/<some-workspace/node_modules)
150+ // and started to be created by Next.js in some cases in next@16.1.0-canary.3
151+ // this node_modules is artificially created and doesn't have equivalent in the repo
152+ // so we only copy it, without additional symlinks handling
153+ if ( existsSync ( join ( srcDir , 'node_modules' ) ) ) {
154+ const filter = ctx . constants . IS_LOCAL ?undefined :nodeModulesFilter
155+ const src = join ( srcDir , 'node_modules' )
156+ const dest = join ( destDir , 'node_modules' )
157+ await cp ( src , dest , {
158+ recursive :true ,
159+ verbatimSymlinks :true ,
160+ force :true ,
161+ filter,
162+ } )
163+ }
164+
165+ await Promise . all ( promises )
149166} )
150167}
151168
@@ -290,42 +307,41 @@ async function patchNextModules(
290307
291308export const copyNextDependencies = async ( ctx :PluginContext ) :Promise < void > => {
292309await tracer . withActiveSpan ( 'copyNextDependencies' , async ( ) => {
293- const entries = await readdir ( ctx . standaloneDir )
294- const filter = ctx . constants . IS_LOCAL ?undefined :nodeModulesFilter
310+ const promises :Promise < void > [ ] = [ ]
311+
312+ const nodeModulesLocationsInStandalone = new Set < string > ( )
313+ const commonFilter = ctx . constants . IS_LOCAL ?undefined :nodeModulesFilter
314+
315+ const dotNextDir = join ( ctx . standaloneDir , ctx . nextDistDir )
316+
317+ await cp ( ctx . standaloneRootDir , ctx . serverHandlerRootDir , {
318+ recursive :true ,
319+ verbatimSymlinks :true ,
320+ force :true ,
321+ filter :async ( sourcePath :string ) => {
322+ if ( sourcePath === dotNextDir ) {
323+ // copy all except the distDir (.next) folder as this is handled in a separate function
324+ // this will include the node_modules folder as well
325+ return false
326+ }
295327
296- const promises :Promise < void > [ ] = entries . map ( async ( entry ) => {
297- // copy all except the distDir (.next) folder as this is handled in a separate function
298- // this will include the node_modules folder as well
299- if ( entry === ctx . nextDistDir ) {
300- return
301- }
302- const src = join ( ctx . standaloneDir , entry )
303- const dest = join ( ctx . serverHandlerDir , entry )
304- await cp ( src , dest , {
305- recursive :true ,
306- verbatimSymlinks :true ,
307- force :true ,
308- filter,
309- } )
328+ if ( sourcePath . endsWith ( 'node_modules' ) ) {
329+ // keep track of node_modules as we might need to recreate symlinks
330+ // we are still copying them
331+ nodeModulesLocationsInStandalone . add ( sourcePath )
332+ }
310333
311- if ( entry === 'node_modules' ) {
312- await recreateNodeModuleSymlinks ( ctx . resolveFromSiteDir ( 'node_modules' ) , dest )
313- }
334+ // finally apply common filter if defined
335+ return commonFilter ?. ( sourcePath ) ?? true
336+ } ,
314337} )
315338
316- // inside a monorepo there is a root `node_modules` folder that contains all the dependencies
317- const rootSrcDir = join ( ctx . standaloneRootDir , 'node_modules' )
318- const rootDestDir = join ( ctx . serverHandlerRootDir , 'node_modules' )
319-
320- // use the node_modules tree from the process.cwd() and not the one from the standalone output
321- // as the standalone node_modules are already wrongly assembled by Next.js.
322- // see: https://github.com/vercel/next.js/issues/50072
323- if ( existsSync ( rootSrcDir ) && ctx . standaloneRootDir !== ctx . standaloneDir ) {
324- promises . push (
325- cp ( rootSrcDir , rootDestDir , { recursive :true , verbatimSymlinks :true , filter} ) . then ( ( ) =>
326- recreateNodeModuleSymlinks ( resolve ( 'node_modules' ) , rootDestDir ) ,
327- ) ,
328- )
339+ for ( const nodeModulesLocationInStandalone of nodeModulesLocationsInStandalone ) {
340+ const relativeToRoot = relative ( ctx . standaloneRootDir , nodeModulesLocationInStandalone )
341+ const locationInProject = join ( ctx . outputFileTracingRoot , relativeToRoot )
342+ const locationInServerHandler = join ( ctx . serverHandlerRootDir , relativeToRoot )
343+
344+ promises . push ( recreateNodeModuleSymlinks ( locationInProject , locationInServerHandler ) )
329345}
330346
331347await Promise . all ( promises )
@@ -451,7 +467,7 @@ export const verifyHandlerDirStructure = async (ctx: PluginContext) => {
451467// https://github.com/pnpm/pnpm/issues/9654
452468// https://github.com/pnpm/pnpm/issues/5928
453469// https://github.com/pnpm/pnpm/issues/7362 (persisting even though ticket is closed)
454- const nodeModulesFilter = async ( sourcePath :string ) => {
470+ const nodeModulesFilter = ( sourcePath :string ) => {
455471// Filtering rule for the following packages:
456472// - @rspack+binding-linux-x64-musl
457473// - @swc+core-linux-x64-musl