Skip to main content

Fixing "Build Failed" Errors for Next.js 14/15 Apps on AWS Amplify

 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:

  1. 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.
  2. Artifact Mismatch: Next.js 14+ optimizes output into a standalone directory or the .next folder. Amplify's default build settings often look for a generic out directory (common in Static Site Generation) or fail to detect the Server-Side Rendering (SSR) compute requirements if the framework is 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 your amplify/ folder.
  • npx ampx generate outputs: This is the missing link. It fetches the configuration from the deployed backend and creates the amplify_outputs.json file in your build environment.
  • baseDirectory: .next: Explicitly tells Amplify to look for the Next.js server build artifacts, not a static out folder.

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:

  1. You are using legacy Gen 1 code snippets in a Gen 2 application.
  2. 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:

  1. Go to App Settings > Build settings.
  2. Under Build image settings, verify the package override is set to verify node version, 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.