diff --git a/.changeset/forty-pets-sell.md b/.changeset/forty-pets-sell.md new file mode 100644 index 000000000..3831c55a7 --- /dev/null +++ b/.changeset/forty-pets-sell.md @@ -0,0 +1,19 @@ +--- +"@opennextjs/aws": patch +--- + +fix: find the correct output for the standalone output + +Open Next assumes that standalone is based on the root of a monorepo, while Next uses `outputFileTracingRoot` to determine the root and path of .next/standalone. For example, take a monorepo like this: + +``` +apps/ + marketing/ + landing-pages/ + // next app + +``` + +If you set `outputFileTracingRoot` in landing pages to ../, then the standalone output would be `.next/standalone/landing-pages`, but open-next will assume that the output is `.next/standalone/apps/marketing/landing-pages`. + +This PR fixes that by actually looking at the standalone output and determining its structure by looking for `.next/standalone/dir/.next` and returning `.next/standalone/dir`. diff --git a/packages/open-next/src/build/helper.ts b/packages/open-next/src/build/helper.ts index 030b309cd..676e286ff 100644 --- a/packages/open-next/src/build/helper.ts +++ b/packages/open-next/src/build/helper.ts @@ -438,5 +438,66 @@ export async function isEdgeRuntime( } export function getPackagePath(options: BuildOptions) { + // First, try to detect the actual package path from the standalone output. + // Next.js uses the package name from package.json to create the folder structure + // inside .next/standalone, which may differ from the filesystem path in monorepos. + const standaloneDir = path.join( + options.appBuildOutputPath, + ".next/standalone", + ); + + if (fs.existsSync(standaloneDir)) { + const detectedPath = findStandalonePackagePath(standaloneDir); + if (detectedPath !== null) { + return detectedPath; + } + } + return path.relative(options.monorepoRoot, options.appBuildOutputPath); } + +/** + * Finds the package path inside the standalone output by looking for a directory + * that contains a `.next` folder. This handles monorepo setups where Next.js + * uses the package name from package.json rather than the filesystem path. + */ +function findStandalonePackagePath(standaloneDir: string): string | null { + // If .next exists directly in standalone dir, it's not a monorepo setup + if (fs.existsSync(path.join(standaloneDir, ".next"))) { + return ""; + } + + // Helper function to recursively search for .next directory + function findNextDir(dir: string, currentPath: string): string | null { + const entries = fs.readdirSync(dir, { withFileTypes: true }); + + for (const entry of entries) { + // Skip node_modules directory + if (entry.name === "node_modules") { + continue; + } + + if (entry.isDirectory()) { + const entryPath = path.join(dir, entry.name); + const relativePath = currentPath + ? path.join(currentPath, entry.name) + : entry.name; + + // Check if this directory contains a .next folder + if (fs.existsSync(path.join(entryPath, ".next"))) { + return relativePath; + } + + // Recursively search subdirectories + const result = findNextDir(entryPath, relativePath); + if (result) { + return result; + } + } + } + + return null; + } + + return findNextDir(standaloneDir, ""); +}