Introduction
You pushed a shiny Next.js app to AWS Amplify. The build passed, logs looked clean, your domain pointed correctly… and then: “500 Internal Server Error.” Ouch.
If your app runs perfectly on localhost
but breaks in Amplify’s runtime, you’re not alone. The culprit is almost never your app logic—it’s runtime configuration: SSR output, environment variables, CORS, image optimization, Node versions, or Amplify caching.
In this guide, you’ll learn a reliable, production-grade process to diagnose and fix 500 errors on AWS Amplify for Next.js. We’ll walk through the exact files to adjust, what Amplify expects for SSR, how to expose environment variables correctly, and how to keep images, APIs, and cache in sync—so your deployment behaves like your local environment.
By the end, you’ll have a repeatable checklist to ship Next.js apps to Amplify without mystery 500s—plus troubleshooting recipes for the edge cases that derail most teams.
What Causes a 500 on Amplify for Next.js?
A 500 means the server crashed or threw an error at runtime. On Amplify, that usually happens because:
- SSR output mismatch — Next.js needs
output: "standalone"
so Amplify knows how to run the server. - ENV variables aren’t available at runtime — they were present locally but not injected in Amplify or not prefixed for the browser.
- CORS or API mismatch — your backend rejects requests from the Amplify domain.
- Image optimization not configured — remote image hosts aren’t allowed in
next.config
. - Wrong Node version or caching — stale cache or incompatible Node runtime breaks the server.
The fix? Align build + runtime to match local dev, then clear caches and redeploy.
The Playbook: Fix Next.js 500 Errors on AWS Amplify
1) Confirm Local Is Healthy (Baseline First)
Before tweaking Amplify, prove the app is solid locally:
rm -rf .next
pnpm install
pnpm dev
Then test a production-like build locally:
pnpm build
pnpm start
If this passes and renders pages, you’ve confirmed the issue is deployment config, not your code.
Local ENV sanity check
Create or verify .env.local
:
NEXT_PUBLIC_SITE_URL=http://localhost:3000
API_URL=https://api.yourdomain.com
NEXT_PUBLIC_IMG_CDN=https://cdn.yourdomain.com
# Add any tokens/keys required by your app at runtime
- Server-only secrets: no
NEXT_PUBLIC_
prefix. - Client-exposed values: must start with
NEXT_PUBLIC_
.
2) Force Standalone Output for SSR
Amplify expects a standalone Next.js server bundle. In next.config.mjs
:
/** @type {import('next').NextConfig} */
const nextConfig = {
reactStrictMode: true,
output: "standalone", // ✅ critical for Amplify SSR
images: {
domains: [
"yourdomain.com",
"cdn.yourdomain.com",
"lh3.googleusercontent.com",
"gravitar.com",
"images.unsplash.com",
"*.amplifyapp.com".replace("*.", "") // wildcard isn't supported, whitelist your app's host if needed
],
remotePatterns: [
{ protocol: "https", hostname: "cdn.yourdomain.com", pathname: "/**" },
{ protocol: "https", hostname: "yourdomain.com", pathname: "/**" },
// add more as needed
],
},
env: {
API_URL: process.env.API_URL,
NEXT_PUBLIC_IMG_CDN: process.env.NEXT_PUBLIC_IMG_CDN,
NEXT_PUBLIC_SITE_URL: process.env.NEXT_PUBLIC_SITE_URL,
},
experimental: {
// only if you actually use specific features; otherwise omit
}
};
export default nextConfig;
Why this matters: Without output: "standalone"
, Amplify may build successfully but fail to execute the server bundle at runtime—leading to a 500.
Pro tip: If you dynamically whitelist image hosts, ensure they’re present in Amplify environment variables (more on that below).
3) Make Environment Variables Available in Amplify
Your Next.js runtime on Amplify doesn’t magically inherit .env.local
. You must set env vars in the Amplify Console → Environment variables or via your CI.
Rules of thumb
- Anything used by the browser must be prefixed with
NEXT_PUBLIC_
. - Anything used only by the server stays unprefixed.
- Changing env vars? Clear Amplify cache and rebuild.
Examples to set in Amplify
API_URL=https://api.yourdomain.com
NEXT_PUBLIC_IMG_CDN=https://cdn.yourdomain.com
NEXT_PUBLIC_SITE_URL=https://main.<AMPLIFY_APP_ID>.amplifyapp.com
NODE_OPTIONS=--max-old-space-size=4096 # optional for memory-heavy builds
4) Use a Stable amplify.yml
for Builds
Provide Amplify a reproducible build pipeline. Here’s a production-friendly baseline for PNPM + Next.js 14:
version: 1
frontend:
phases:
preBuild:
commands:
- npm i -g pnpm@9.6.0
- pnpm install --frozen-lockfile
build:
commands:
- echo "Building Next.js standalone output..."
- pnpm run build
artifacts:
baseDirectory: .next
files:
- '**/*'
cache:
paths:
- node_modules/**/*
- .pnpm-store/**/*
- .next/cache/**/*
buildImage: amplify:al2023
nextjs:
nextVersion: 14
pnpmVersion: 9.6.0
Common gotchas
- If you switch lockfile manager or Node versions, clear cache in Amplify’s console.
- If your app needs a specific Node version, add an
.nvmrc
at repo root with18
or20
.
5) Fix CORS (If You Call an External API)
If your app talks to a backend (e.g., Node, Rails, Laravel), add your Amplify domain and custom domain to the backend’s CORS allowlist.
Example CORS rules (conceptual):
- Allow origins:
https://main.<AMPLIFY_APP_ID>.amplifyapp.com
,https://www.yourdomain.com
,http://localhost:3000
- Allow headers:
*
- Allow methods:
GET, POST, PATCH, DELETE, OPTIONS
- Credentials: only if you need auth cookies
Why it breaks: Locally, your origin is http://localhost:3000
. In production, it’s the Amplify domain or your custom domain. If your backend doesn’t recognize that origin, it rejects requests—sometimes surfacing as a 500 in your app.
6) Configure Image Optimization
Next.js blocks remote images from untrusted domains. On Amplify, your image URLs might differ from local (e.g., CDN links). Add them to images.domains
or remotePatterns
.
images: {
domains: ["cdn.yourdomain.com", "avatars.githubusercontent.com"],
remotePatterns: [
{ protocol: "https", hostname: "cdn.yourdomain.com", pathname: "/**" },
],
}
Symptoms of misconfig: images failing silently, hydration mismatches, or server errors if an image loader fails during SSR.
7) Clear Amplify Cache and Re-Deploy Cleanly
Amplify caches dependencies and .next/cache
. When you change env vars, next.config.mjs
, or lockfile details, always:
- Amplify Console → Build settings → Clear cache
- Trigger a Redeploy
This forces a clean installation and avoids stale artifacts that can cause runtime crashes.
8) Verify Node Runtime and Memory
- Add
.nvmrc
with a supported version (e.g.,18
). - If builds are heavy, set
NODE_OPTIONS=--max-old-space-size=4096
in Amplify env vars. - Keep dependencies lean and check for native modules that require special runtime handling.
9) SSR vs. Static: Know What You’re Deploying
- SSG/ISR pages generally serve fine with Amplify’s static hosting + Next server.
- SSR routes require the standalone server to run smoothly.
- If you have edge functions or advanced middleware, confirm compatibility with Amplify’s runtime.
Step-by-Step: From 500 to 200 OK
A. Minimal Working next.config.mjs
/** @type {import('next').NextConfig} */
const nextConfig = {
output: "standalone",
images: {
domains: ["cdn.yourdomain.com", "yourdomain.com", "lh3.googleusercontent.com"],
remotePatterns: [
{ protocol: "https", hostname: "cdn.yourdomain.com", pathname: "/**" },
],
},
env: {
API_URL: process.env.API_URL,
NEXT_PUBLIC_IMG_CDN: process.env.NEXT_PUBLIC_IMG_CDN,
NEXT_PUBLIC_SITE_URL: process.env.NEXT_PUBLIC_SITE_URL,
},
reactStrictMode: true,
};
export default nextConfig;
B. Production Build Commands
pnpm install --frozen-lockfile
pnpm run build
Then in Amplify, set:
API_URL
NEXT_PUBLIC_IMG_CDN
NEXT_PUBLIC_SITE_URL
- (Optional)
NODE_OPTIONS=--max-old-space-size=4096
C. CORS Allowlist (Backend)
- Add both the Amplify preview domain and your custom domain.
- Redeploy backend changes and flush config caches if your framework caches configs.
D. Invalidate Amplify Cache
- Amplify Console → Clear cache
- Redeploy → Test the live URL
Deep-Dive Troubleshooting (When It’s Still 500)
1) Check Server Runtime Logs
-
Open Amplify build logs and app logs.
-
Look for errors like:
ReferenceError: window is not defined
(SSR code accessing browser APIs)TypeError: fetch failed
(backend not reachable or CORS blocked)ENOENT: no such file or directory
(paths that exist locally but not in build output)
Fix: Guard browser-only code with typeof window !== 'undefined'
or use dynamic imports with ssr: false
.
2) Hydration Mismatch Causing Server Error
- Conditional rendering or locales can cause crashes during SSR.
- Verify that any data fetched in
getServerSideProps
is reachable with current env vars.
Fix: Log process.env
keys existence (not values) in dev; ensure all needed vars are set in Amplify.
3) API URLs Are Relative in SSR
- Relative paths that work in the browser may fail on the server.
Fix: Use absolute URLs sourced from env:
const base = process.env.API_URL!;
const res = await fetch(`${base}/posts`);
4) Image Domains Not Whitelisted
- 500s can surface from image optimizer failures.
Fix: Add all remote hosts to images.domains
or remotePatterns
.
5) Node Version Mismatch
- If local runs Node 20 but Amplify defaults to 18, native packages may misbehave.
Fix: Add .nvmrc
with the version you develop on, and align your local Node to match.
6) ISR/Cache Invalidation
- If using ISR with
revalidate
, ensure revalidation tokens and routes are set up correctly. - Stale renders can hide misconfig until a path is revalidated and triggers a server fetch that fails.
Fix: Confirm fetch
endpoints, auth headers, and environment tokens in Amplify.
7) Middleware or Edge Cases
- Remove or simplify
middleware.ts
temporarily to isolate runtime failures. - If you rely on advanced headers/cookies, verify Amplify forwards them as expected.
Comparisons: Amplify vs Local
| Area | Local Dev (pnpm dev
) | Amplify Runtime |
| ------------ | ----------------------------- | --------------------------------------- |
| ENV loading | .env.local
| Must be set in Amplify env vars |
| Images | Any host will “seem” to work | Must whitelist hosts |
| SSR Output | Dev server | Requires output: "standalone"
|
| CORS | Often permissive or localhost | Must allow Amplify domain(s) |
| Node Version | Whatever’s on your machine | Controlled via .nvmrc
/build image |
| Cache | None or ephemeral | Amplify caches dependencies and .next
|
Takeaway: Your app didn’t break—the environment changed. Align the runtime and 500s disappear.
Bullet Points / Quick Takeaways
- Set
output: "standalone"
innext.config.mjs
for Amplify SSR. - Mirror
.env.local
in Amplify Environment variables; prefix browser vars withNEXT_PUBLIC_
. - Whitelist your Amplify and custom domain in backend CORS.
- Add all CDN/API hosts to Next.js image config.
- Keep Node versions aligned with
.nvmrc
and declare PNPM version. - Clear Amplify cache after env or build config changes.
- Use absolute URLs in SSR (
API_URL
), not relative paths. - Guard browser-only code during SSR to avoid runtime crashes.
- Check Amplify logs for stack traces; fix the exact failing point.
- Re-deploy from a clean state once fixes are in.
Call to Action (CTA)
Want to deploy your Next.js app smoothly on AWS Amplify — without 500 errors, CORS issues, or broken builds? If you’d rather have an expert handle the setup for you, hire me on Fiverr to configure, debug, and optimize your Amplify deployment for production-ready performance.
👉 Get professional Next.js + AWS Amplify setup support on Fiverr
Optional: FAQ
Q1: My Amplify build succeeds but the live site shows 500. Why?
The runtime can’t execute your app as built—usually due to missing output: "standalone"
, missing env vars at runtime, CORS rejection, or image host restrictions.
Q2: Do I need to prefix environment variables?
Yes, only variables used in the browser must be prefixed with NEXT_PUBLIC_
. Server-only variables should not be prefixed.
Q3: How do I allow images from my CDN?
Add the host to images.domains
or remotePatterns
in next.config.mjs
. Without this, image optimization can fail and trigger runtime issues.
Q4: What Node version should I use?
Use a stable, supported version (Node 18 or 20). Add an .nvmrc
file so Amplify uses the same version as local development.
Q5: I fixed env and CORS but still get 500. What next? Clear Amplify cache, redeploy, and inspect logs. Verify absolute API URLs in SSR, guard browser-only code, and check for middleware/edge function incompatibilities.
Q6: Can I avoid 500s on future deploys? Yes—lock in the playbook: standalone output, synced env vars, CORS allowlist, image hosts, Node alignment, and cache clears for config changes.