-
Notifications
You must be signed in to change notification settings - Fork 121
Description
Environment
- Hosting: AWS Amplify
- Framework: Next.js 16.1.0
- Node.js version: 22.x
- Package manager: pnpm 10.26.1
- Region: ap-northeast-1
Describe the bug
When deploying a Next.js 16.1.0 application to AWS Amplify, the build succeeds but the bundling phase fails with an EEXIST error related to symlinks in .next/node_modules/.
Root Cause
Next.js 16.1 introduced changes to Turbopack's module resolution that creates hashed symlinks in the .next/node_modules/ directory during the build process.
For example:
.next/node_modules/pino-3de069a0e16ae0ec -> ../../node_modules/pino
This behavior change was introduced between Next.js 16.0.10 and 16.1.0 (see vercel/next.js#87402 for similar Turbopack symlink issues).
AWS Amplify's bundler cannot handle these symlinks properly, causing EEXIST errors when it attempts to create directories that conflict with existing symlinks during the compute bundle process.
Note: This may be related to the Improved Handling of serverExternalPackages feature in Next.js 16.1, which enhanced Turbopack's handling of transitive dependencies.
Build Logs
2025-12-19T16:24:11.137Z [INFO]: # Starting caching...
2025-12-19T16:24:11.142Z [INFO]: # Creating cache artifact...
2025-12-19T16:24:11.146Z [INFO]: # Created cache artifact
2025-12-19T16:24:11.147Z [INFO]: # Uploading cache artifact...
2025-12-19T16:24:11.253Z [INFO]: # Uploaded cache artifact
2025-12-19T16:24:11.253Z [INFO]: # Caching completed
2025-12-19T16:24:31.471Z [ERROR]: !!! We failed to bundle your application due to an unexpected error. Please try again. If the issue persists, please contact AWS support.
2025-12-19T16:24:31.591Z [ERROR]: !!! Error: EEXIST: file already exists, mkdir '/codebuild/output/src210259080/src/****/frontend/amplify-compute-bundle-output/compute/default/.next/node_modules/pino-3de069a0e16ae0ec'
2025-12-19T16:24:31.592Z [INFO]: # Starting environment caching...
2025-12-19T16:24:31.592Z [INFO]: # Environment caching completed
Expected behavior
Amplify's bundler should correctly handle symlinks created by Next.js 16.1+ Turbopack, or dereference them during the bundling process.
Steps to reproduce
- Create a Next.js 16.1.0 application (Turbopack is enabled by default)
- Include a package that Turbopack externalizes (e.g.,
pino,jsdom, or other native modules) - Deploy to AWS Amplify
- Build succeeds, but bundling phase fails with
EEXISTerror
Workaround
We resolved this by adding a post-build script that resolves symlinks to actual directories before Amplify's bundler runs.
amplify.yml:
version: 1
applications:
- appRoot: frontend
frontend:
phases:
preBuild:
commands:
- corepack enable
- pnpm install --frozen-lockfile
build:
commands:
- pnpm build
# Next.js 16.1 Turbopack creates hashed symlinks that Amplify can't handle
- node scripts/resolve-next-symlinks.js
artifacts:
baseDirectory: .next
files:
- "**/*"
cache:
paths:
- node_modules/**/*
- .next/cache/**/*scripts/resolve-next-symlinks.js:
/**
* Next.js 16.1 Turbopack Symlink Resolver for AWS Amplify
*
* Next.js 16.1's Turbopack creates hashed symlinks for externalized packages
* (e.g., pino-3de069a0e16ae0ec -> ../../node_modules/pino)
*
* AWS Amplify's bundler cannot handle these symlinks properly, causing
* "EEXIST: file already exists" errors during deployment.
*
* This script resolves symlinks to real directories and copies their dependencies.
*/
const fs = require('fs');
const path = require('path');
const nextModules = path.join(__dirname, '..', '.next', 'node_modules');
const rootModules = path.join(__dirname, '..', 'node_modules');
function resolveDependencyPath(depName, parentPkgPath) {
const directPath = path.join(rootModules, depName);
try {
const stat = fs.lstatSync(directPath);
if (stat.isSymbolicLink()) {
return fs.realpathSync(directPath);
}
if (stat.isDirectory()) {
return directPath;
}
} catch {
// Not found at root level
}
// For pnpm's isolated mode, check the parent package's node_modules
if (parentPkgPath) {
const parentNodeModules = path.dirname(parentPkgPath);
const pnpmDepPath = path.join(parentNodeModules, depName);
try {
const stat = fs.lstatSync(pnpmDepPath);
if (stat.isSymbolicLink()) {
return fs.realpathSync(pnpmDepPath);
}
if (stat.isDirectory()) {
return pnpmDepPath;
}
} catch {
// Not found in parent's node_modules
}
}
return null;
}
function copyPackageWithDeps(pkgPath, destPath, copiedSet, originalPkgPath) {
const pkgName = path.basename(destPath);
if (copiedSet.has(pkgName)) {
return 0;
}
copiedSet.add(pkgName);
console.log(` Copying: ${pkgName}`);
fs.cpSync(pkgPath, destPath, { recursive: true, dereference: true });
let count = 1;
const pkgJsonPath = path.join(destPath, 'package.json');
if (fs.existsSync(pkgJsonPath)) {
const pkg = JSON.parse(fs.readFileSync(pkgJsonPath, 'utf8'));
const deps = Object.keys(pkg.dependencies || {});
for (const dep of deps) {
const depDest = path.join(nextModules, dep);
if (!fs.existsSync(depDest) && !copiedSet.has(dep)) {
const depSrc = resolveDependencyPath(dep, originalPkgPath || pkgPath);
if (depSrc) {
count += copyPackageWithDeps(depSrc, depDest, copiedSet, depSrc);
} else {
console.log(` Warning: Could not find dependency ${dep}`);
}
}
}
}
return count;
}
function main() {
if (!fs.existsSync(nextModules)) {
console.log('No .next/node_modules directory found, skipping.');
return;
}
const entries = fs.readdirSync(nextModules);
let resolved = 0;
const copiedSet = new Set();
for (const name of entries) {
const linkPath = path.join(nextModules, name);
const stat = fs.lstatSync(linkPath);
if (stat.isSymbolicLink()) {
const target = fs.realpathSync(linkPath);
console.log(`Resolving: ${name} -> ${target}`);
fs.rmSync(linkPath);
copyPackageWithDeps(target, linkPath, copiedSet, target);
resolved++;
}
}
console.log(`\nResolved ${resolved} symlinks, copied ${copiedSet.size} packages total.`);
}
main();Suggested Fix
AWS Amplify should either:
- Dereference symlinks during bundling - Use
--dereferenceor equivalent when copying/bundling the.nextdirectory - Update the bundler to properly handle symlinks created by modern build tools like Turbopack
- Document this limitation for Next.js 16.1+ users until a fix is available
Related Issues
- vercel/next.js#87402 - Turbopack symlink creation issue in Next.js 16.1 (Windows)
- aws-amplify/amplify-js#14600 - Next.js 16 Support request
Additional Context
- This affects any Next.js 16.1+ project where Turbopack externalizes packages (e.g.,
pino,jsdom, native modules) - The issue is specific to AWS Amplify's bundler, not the Next.js build itself
- The Next.js build completes successfully; the failure occurs during Amplify's compute bundle phase
- The workaround works for npm, yarn, and pnpm package managers
- Turbopack is enabled by default in Next.js 16, so this will affect most Next.js 16.1+ deployments