There are few things more frustrating in modern web development than a locally working application that explodes immediately upon deployment.
If you are deploying a Next.js 14 or 15 application (specifically using the App Router) to AWS Amplify Gen 2, you have likely encountered the dreaded "Build Failed" status. The logs often point to a missing configuration module (Cannot find module 'aws-exports' or amplify_outputs.json) or a generic failure during the build phase.
This guide moves beyond generic troubleshooting. We will dissect the root cause of the specific conflict between the Next.js build artifact structure and the Amplify CI/CD pipeline, and provide the precise amplify.yml configurations and code adjustments required to fix it.
The Root Cause: The "Chicken and Egg" Config Problem
To solve this, you must understand the architecture of an Amplify Gen 2 deployment.
In a standard local environment, you run npx ampx sandbox. This spins up temporary AWS resources and writes a configuration file (usually amplify_outputs.json in Gen 2) directly to your file system. Your Next.js frontend imports this file to initialize the Amplify client.
The failure happens in CI/CD for two reasons:
- Missing Build Context: When Amplify's build system clones your repo, the configuration file is (correctly) git-ignored. The frontend build script runs
next build. This script attempts to compile your code. If your code imports the config file, and the backend deployment hasn't finished (or hasn't written the file to the correct path), the build crashes immediately. - Artifact Mismatch: Next.js 14+ optimizes output into a standalone directory or the
.nextfolder. Amplify's default build settings often look for a genericoutdirectory (common in Static Site Generation) or fail to detect the Server-Side Rendering (SSR) compute requirements if theframeworkis not correctly detected.
Solution 1: Correcting the Build Specification (amplify.yml)
The most common point of failure is an incorrect amplify.yml. The default configuration often fails to explicitly generate the outputs before the frontend build runs.
You must override the build settings to explicitly chain the backend deployment output generation before the frontend build command.
Create or update amplify.yml in your project root:
version: 1
backend:
phases:
build:
commands:
- npm ci --cache .npm --prefer-offline
- npx ampx pipeline-deploy --branch $AWS_BRANCH --app-id $AWS_APP_ID
frontend:
phases:
preBuild:
commands:
- npm ci --cache .npm --prefer-offline
build:
commands:
# CRITICAL: This ensures the config exists before Next.js compiles
- npx ampx generate outputs --branch $AWS_BRANCH --app-id $AWS_APP_ID
- npm run build
artifacts:
baseDirectory: .next
files:
- '**/*'
cache:
paths:
- .next/cache/**/*
- .npm/**/*
Why this works:
npx ampx pipeline-deploy: Deploys the backend resources defined in youramplify/folder.npx ampx generate outputs: This is the missing link. It fetches the configuration from the deployed backend and creates theamplify_outputs.jsonfile in your build environment.baseDirectory: .next: Explicitly tells Amplify to look for the Next.js server build artifacts, not a staticoutfolder.
Solution 2: Fixing the "Cannot find module aws-exports" Error
If your build logs specifically say Cannot find module './aws-exports', you are encountering a Generation Mismatch.
Amplify Gen 2 uses amplify_outputs.json by default. If your code is trying to import aws-exports, one of two things is happening:
- You are using legacy Gen 1 code snippets in a Gen 2 application.
- You haven't updated your Client Component configuration.
The Fix: Create a Client Config Wrapper
In Next.js 14/15 App Router, you cannot initialize Amplify in layout.tsx (a Server Component) directly if you rely on browser APIs or standard imports.
Create a robust client configuration component.
File: components/ConfigureAmplify.tsx
'use client';
import { Amplify } from 'aws-amplify';
import outputs from '@/amplify_outputs.json'; // Gen 2 config
// Check if the file was imported correctly to prevent runtime crashes
if (!outputs) {
console.error("Amplify outputs are missing. Check amplify.yml generation step.");
} else {
Amplify.configure(outputs, { ssr: true });
}
export default function ConfigureAmplifyClientSide() {
return null;
}
File: app/layout.tsx
import type { Metadata } from "next";
import { Inter } from "next/font/google";
import "./globals.css";
import ConfigureAmplifyClientSide from "@/components/ConfigureAmplify";
const inter = Inter({ subsets: ["latin"] });
export const metadata: Metadata = {
title: "Next.js Amplify Gen 2",
description: "Robust Deployment Architecture",
};
export default function RootLayout({
children,
}: {
children: React.ReactNode;
}) {
return (
<html lang="en">
<body className={inter.className}>
{/* Initialize Amplify on the client */}
<ConfigureAmplifyClientSide />
{children}
</body>
</html>
);
}
Note: Ensure your tsconfig.json includes "resolveJsonModule": true to allow importing the JSON configuration file.
Solution 3: Environment & Node Version Mismatch
Next.js 14 requires Node.js 18.17 or later. AWS Amplify build containers sometimes default to Amazon Linux 2 images running older Node versions.
If your build fails with syntax errors or "unexpected token" errors inside node_modules, you are likely on an old Node version.
The Fix: Force Node Version in Package.json
Add an engines field to your package.json. Amplify inspects this file to select the build image.
{
"name": "my-amplify-app",
"version": "0.1.0",
"private": true,
"engines": {
"node": ">=18.17.0"
},
"scripts": {
"dev": "next dev",
"build": "next build",
"start": "next start",
"lint": "next lint"
}
}
Additionally, inside the AWS Amplify Console:
- Go to App Settings > Build settings.
- Under Build image settings, verify the package override is set to verify
nodeversion, or click Edit and set the Live Package Update to utilize Node 20.
Deep Dive: Handling SSR vs. Static Output
A frequent misconfiguration occurs when developers accidentally mix Static Site Generation (SSG) with Server-Side Rendering (SSR).
If your next.config.js contains output: 'export', Next.js will ignore server endpoints and produce a static HTML folder (usually out).
If you are using Amplify's Hosting compute (for API routes, dynamic headers, or getServerSideProps equivalent logic), you must remove output: 'export' from your config.
Correct next.config.mjs for SSR:
/** @type {import('next').NextConfig} */
const nextConfig = {
// Do NOT use output: 'export' for SSR deployments
images: {
remotePatterns: [
{
protocol: 'https',
hostname: 'example.com', // Replace with your S3 bucket or domain
},
],
},
};
export default nextConfig;
If you fail to remove output: 'export' but define baseDirectory: .next in your amplify.yml, the build will succeed, but the deployment will fail because the artifacts expected by the Amplify Compute handler are missing.
Common Edge Cases
1. Monorepos (Nx / Turborepo)
If you are in a monorepo, the root amplify.yml needs to point to the specific app root. Use the appRoot property:
version: 1
applications:
- appRoot: apps/web
frontend:
phases:
build:
commands:
- npm run build
artifacts:
baseDirectory: .next
files:
- '**/*'
2. The "Cookie" Header Issue
When using SSR, Amplify needs to handle cookies securely. If you notice authentication persistence issues after a successful deploy, ensure you are using the createServerRunner adapter provided by @aws-amplify/adapter-nextjs for server-side API calls, rather than standard fetch requests.
Conclusion
Fixing "Build Failed" on Amplify usually comes down to synchronization. The backend resources must exist before the frontend compiles, the configuration file must be generated in the correct location, and the Node environment must match the requirements of Next.js 14/15.
By explicitly defining the generate outputs step in your amplify.yml and modernizing your client-side configuration entry point, you eliminate the ambiguity that causes 90% of deployment failures.