Deploy Next.js Apps to AWS with Amplify (Complete Guide)
Introduction
AWS Amplify has revolutionized how developers deploy and manage modern web applications, offering a complete solution for full-stack development with built-in CI/CD, hosting, and backend services. For Next.js applications, Amplify provides seamless deployment with automatic builds, preview environments, and global CDN distribution.
This comprehensive guide will walk you through deploying Next.js applications to AWS Amplify, from initial setup to advanced configuration. Whether you're building a static site, server-side rendered application, or full-stack app with API routes, you'll learn everything needed to leverage Amplify's powerful features for production deployments.
Why Choose AWS Amplify for Next.js?
Key Benefits
AWS Amplify offers compelling advantages for Next.js applications:
// Amplify Benefits for Next.js
const AmplifyBenefits = {
deployment: "Git-based CI/CD with automatic builds",
performance: "Global CDN with edge caching",
environments: "Branch-based preview environments",
backend: "Integrated backend services (Auth, API, Storage)",
monitoring: "Built-in analytics and performance insights",
cost: "Pay-per-use pricing with generous free tier",
};
// Supported Next.js Features
const NextjsSupport = {
staticGeneration: "Static Site Generation (SSG)",
serverSideRendering: "Server-Side Rendering (SSR)",
apiRoutes: "API Routes with Lambda functions",
imageOptimization: "Automatic image optimization",
incremental: "Incremental Static Regeneration (ISR)",
};
When to Use Amplify vs Other Solutions
Choose Amplify when you need:
- Rapid deployment with minimal configuration
- Branch-based development workflows
- Integrated backend services
- Built-in authentication and database solutions
- Team collaboration features
Consider alternatives for:
- Complex enterprise deployments
- Specific infrastructure requirements
- Non-standard build processes
Prerequisites and Setup
Required Tools and Accounts
Before starting, ensure you have:
# Required installations
node --version # Node.js 18+ recommended
npm --version # or yarn/pnpm
git --version # Git for version control
# AWS CLI (optional but recommended)
aws --version
Account Requirements:
- AWS account with appropriate permissions
- GitHub/GitLab/Bitbucket repository
- Domain name (optional, for custom domains)
Project Structure Considerations
Amplify works best with standard Next.js project structures:
my-nextjs-app/
├── pages/ # Pages directory (App Router or Pages Router)
├── app/ # App Router (Next.js 13+)
├── public/ # Static assets
├── components/ # React components
├── styles/ # CSS/styling files
├── lib/ # Utility functions
├── package.json # Dependencies and scripts
├── next.config.js # Next.js configuration
└── amplify.yml # Amplify build settings (optional)
Step 1: Preparing Your Next.js Application
Create or Optimize Your Next.js App
If starting fresh, create a new Next.js application:
# Create new Next.js app
npx create-next-app@latest my-amplify-app
cd my-amplify-app
# Or with specific options
npx create-next-app@latest my-amplify-app \
--typescript \
--tailwind \
--eslint \
--app \
--src-dir \
--import-alias "@/*"
Configure Next.js for Amplify
Create or update your next.config.js
for optimal Amplify compatibility:
/** @type {import('next').NextConfig} */
const nextConfig = {
// Enable static exports for SSG (optional)
output: "export",
// Optimize images for Amplify
images: {
unoptimized: true, // Required for static export
// Or configure for Amplify's image optimization
domains: ["your-amplify-domain.amplifyapp.com"],
},
// Trailing slash for better routing
trailingSlash: true,
// Environment variables
env: {
CUSTOM_KEY: process.env.CUSTOM_KEY,
},
// Headers for security and performance
async headers() {
return [
{
source: "/(.*)",
headers: [
{
key: "X-Content-Type-Options",
value: "nosniff",
},
{
key: "X-Frame-Options",
value: "DENY",
},
{
key: "X-XSS-Protection",
value: "1; mode=block",
},
],
},
];
},
};
module.exports = nextConfig;
Optimize Package.json Scripts
Ensure your package.json
includes necessary build scripts:
{
"scripts": {
"dev": "next dev",
"build": "next build",
"start": "next start",
"lint": "next lint",
"export": "next export"
},
"dependencies": {
"next": "latest",
"react": "latest",
"react-dom": "latest"
}
}
Step 2: Setting Up AWS Amplify
Option 1: Deploy via Amplify Console (Recommended for Beginners)
Connect Your Repository
-
Access Amplify Console
- Log into AWS Console
- Navigate to AWS Amplify
- Click "New app" → "Host web app"
-
Connect Git Repository
# Ensure your code is pushed to Git git add . git commit -m "Initial Next.js setup for Amplify" git push origin main
-
Configure Build Settings
- Select your Git provider (GitHub, GitLab, Bitbucket)
- Authorize AWS Amplify access
- Choose repository and branch
- Review detected framework (Next.js)
Build Configuration
Amplify automatically detects Next.js and suggests build settings:
# amplify.yml (auto-generated, customizable)
version: 1
frontend:
phases:
preBuild:
commands:
- npm ci
build:
commands:
- npm run build
artifacts:
baseDirectory: .next
files:
- "**/*"
cache:
paths:
- node_modules/**/*
- .next/cache/**/*
Option 2: Deploy via Amplify CLI (Advanced Users)
Install and Configure Amplify CLI
# Install Amplify CLI globally
npm install -g @aws-amplify/cli
# Configure CLI with AWS credentials
amplify configure
Initialize Amplify Project
# Initialize Amplify in your Next.js project
amplify init
# Follow the prompts:
# - Enter a name for the project
# - Enter a name for the environment (dev, staging, prod)
# - Choose your default editor
# - Choose the type of app (javascript)
# - Select framework (react)
# - Source Directory Path (src or .)
# - Distribution Directory Path (.next)
# - Build Command (npm run build)
# - Start Command (npm run start)
Add Hosting
# Add hosting to your Amplify project
amplify add hosting
# Choose hosting type:
# - Amplify Console (Recommended)
# - Amazon CloudFront and S3
Step 3: Advanced Configuration
Custom Build Settings
For complex applications, create a custom amplify.yml
:
version: 1
frontend:
phases:
preBuild:
commands:
# Install dependencies
- npm ci
# Run any pre-build scripts
- npm run prebuild
build:
commands:
# Set environment for build
- echo "Building for environment $AWS_BRANCH"
# Run build command
- npm run build
# Run tests (optional)
- npm run test:ci
postBuild:
commands:
# Post-build optimization
- echo "Build completed successfully"
artifacts:
baseDirectory: .next
files:
- "**/*"
cache:
paths:
- node_modules/**/*
- .next/cache/**/*
- public/images/**/*
Environment Variables
Configure environment variables for different environments:
// In Amplify Console or via CLI
const environmentVariables = {
// Build-time variables
NEXT_PUBLIC_API_URL: process.env.NEXT_PUBLIC_API_URL,
NEXT_PUBLIC_ANALYTICS_ID: process.env.NEXT_PUBLIC_ANALYTICS_ID,
// Server-side variables
DATABASE_URL: process.env.DATABASE_URL,
API_SECRET_KEY: process.env.API_SECRET_KEY,
};
Set via Amplify Console:
- Go to App Settings → Environment Variables
- Add your variables for each environment
- Variables starting with
NEXT_PUBLIC_
are exposed to the browser
Custom Headers and Redirects
Configure routing and security headers:
# In amplify.yml - Custom headers
customHeaders:
- pattern: "**/*"
headers:
- key: "X-Frame-Options"
value: "DENY"
- key: "X-Content-Type-Options"
value: "nosniff"
- key: "Strict-Transport-Security"
value: "max-age=31536000; includeSubdomains"
# Redirects for SPA routing
redirects:
- source: "/<*>"
target: "/index.html"
status: "404-200"
Step 4: Handling Different Next.js Rendering Methods
Static Site Generation (SSG)
For static sites with getStaticProps
:
// pages/index.js
export async function getStaticProps() {
// Fetch data at build time
const data = await fetchBuildTimeData();
return {
props: {
data,
},
// Regenerate page at most once per day
revalidate: 86400,
};
}
export default function HomePage({ data }) {
return (
<div>
<h1>Static Generated Page</h1>
<pre>{JSON.stringify(data, null, 2)}</pre>
</div>
);
}
Amplify configuration for SSG:
version: 1
frontend:
phases:
preBuild:
commands:
- npm ci
build:
commands:
- npm run build
# For static export
- npm run export
artifacts:
baseDirectory: out # or .next for SSG without export
files:
- "**/*"
Server-Side Rendering (SSR)
For SSR with getServerSideProps
:
// pages/dynamic.js
export async function getServerSideProps(context) {
// This runs on each request
const data = await fetchDynamicData(context.query);
return {
props: {
data,
},
};
}
export default function DynamicPage({ data }) {
return (
<div>
<h1>Server-Side Rendered Page</h1>
<pre>{JSON.stringify(data, null, 2)}</pre>
</div>
);
}
Amplify automatically handles SSR with Lambda@Edge functions.
API Routes
Next.js API routes work seamlessly with Amplify:
// pages/api/hello.js
export default function handler(req, res) {
res.status(200).json({
message: 'Hello from Amplify!',
timestamp: new Date().toISOString(),
method: req.method,
});
}
// pages/api/users/[id].js - Dynamic API routes
export default function handler(req, res) {
const { id } = req.query;
res.status(200).json({
user: { id, name: `User ${id}` },
});
}
Step 5: Branch-Based Deployments
Setting Up Multiple Environments
Configure different branches for different environments:
# Create and switch to development branch
git checkout -b develop
git push -u origin develop
# Create staging branch
git checkout -b staging
git push -u origin staging
In Amplify Console:
- Connect additional branches
- Configure different environment variables per branch
- Set up pull request previews
Environment-Specific Configuration
// next.config.js
const isDevelopment = process.env.NODE_ENV === "development";
const isProduction = process.env.NODE_ENV === "production";
const branch = process.env.AWS_BRANCH;
const nextConfig = {
// Different configurations per environment
env: {
API_URL:
branch === "main"
? "https://api.production.com"
: branch === "staging"
? "https://api.staging.com"
: "https://api.dev.com",
},
// Enable source maps in non-production
productionBrowserSourceMaps: !isProduction,
};
module.exports = nextConfig;
Step 6: Custom Domain Setup
Adding a Custom Domain
-
In Amplify Console:
- Go to App Settings → Domain Management
- Click "Add domain"
- Enter your domain name
-
Configure DNS:
# If using Route 53, Amplify can auto-configure # For other providers, add these records: # Type: CNAME # Name: www # Value: your-branch-name.d1234abcd.amplifyapp.com # Type: A # Name: @ # Value: [Amplify IP addresses provided]
-
SSL Certificate:
- Amplify automatically provisions SSL certificates
- Wait for certificate validation (can take up to 24 hours)
Advanced Domain Configuration
# amplify.yml - Custom domain settings
customHeaders:
- pattern: "**/*"
headers:
- key: "Strict-Transport-Security"
value: "max-age=63072000; includeSubdomains; preload"
redirects:
# Force HTTPS
- source: "http://example.com"
target: "https://example.com"
status: "301"
# Redirect www to non-www
- source: "https://www.example.com"
target: "https://example.com"
status: "301"
Step 7: Performance Optimization
Build Optimization
Optimize your builds for Amplify:
// next.config.js - Performance optimizations
const nextConfig = {
// Compress images
images: {
formats: ["image/avif", "image/webp"],
deviceSizes: [640, 750, 828, 1080, 1200, 1920, 2048, 3840],
},
// Enable SWC minification
swcMinify: true,
// Experimental features
experimental: {
// Enable modern builds
modern: true,
// Optimize CSS
optimizeCss: true,
},
// Webpack optimizations
webpack: (config, { dev, isServer }) => {
if (!dev && !isServer) {
// Production client-side optimizations
config.optimization.splitChunks = {
chunks: "all",
cacheGroups: {
vendor: {
test: /[\\/]node_modules[\\/]/,
name: "vendors",
priority: 10,
enforce: true,
},
},
};
}
return config;
},
};
Caching Strategies
# amplify.yml - Advanced caching
version: 1
frontend:
phases:
preBuild:
commands:
- npm ci
build:
commands:
- npm run build
artifacts:
baseDirectory: .next
files:
- "**/*"
cache:
paths:
- node_modules/**/*
- .next/cache/**/*
- public/**/*
# Custom cache headers
customHeaders:
- pattern: "/static/**"
headers:
- key: "Cache-Control"
value: "public, max-age=31536000, immutable"
- pattern: "/**/*.js"
headers:
- key: "Cache-Control"
value: "public, max-age=31536000, immutable"
- pattern: "/**/*.css"
headers:
- key: "Cache-Control"
value: "public, max-age=31536000, immutable"
Step 8: Monitoring and Analytics
Built-in Analytics
Enable Amplify analytics:
// In your Next.js app
import { Amplify, Analytics } from "aws-amplify";
// Configure Amplify (if using backend services)
Amplify.configure({
Analytics: {
// Auto-track page views
autoTrack: {
enable: true,
type: "pageView",
},
},
});
// Track custom events
const trackCustomEvent = () => {
Analytics.record({
name: "button_click",
attributes: {
page: "homepage",
action: "cta_click",
},
});
};
Performance Monitoring
Set up comprehensive monitoring:
// lib/monitoring.js
export const reportWebVitals = (metric) => {
// Report to analytics service
if (typeof window !== "undefined") {
console.log(metric);
// Send to your analytics service
fetch("/api/analytics", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(metric),
});
}
};
// pages/_app.js
export { reportWebVitals } from "../lib/monitoring";
Step 9: CI/CD and Automation
Advanced Build Hooks
Set up webhooks for external triggers:
# Trigger builds via webhook
curl -X POST https://webhooks.amplify.aws.com/prod/webhooks/[webhook-id] \
-H "Content-Type: application/json" \
-d '{"branch": "main"}'
Automated Testing
# amplify.yml with testing
version: 1
frontend:
phases:
preBuild:
commands:
- npm ci
# Run linting
- npm run lint
# Run type checking
- npm run type-check
build:
commands:
# Run tests
- npm run test:ci
# Build the application
- npm run build
postBuild:
commands:
# Run integration tests
- npm run test:integration
# Generate build report
- npm run build:analyze
Deployment Scripts
Automate common tasks:
// scripts/deploy.js
const { exec } = require("child_process");
const deployBranch = async (branch) => {
try {
// Run pre-deployment checks
exec("npm run lint && npm run test", (error) => {
if (error) throw error;
// Deploy to specific branch
exec(`git push origin ${branch}`, (error) => {
if (error) throw error;
console.log(`Successfully deployed to ${branch}`);
});
});
} catch (error) {
console.error("Deployment failed:", error);
}
};
// Usage: node scripts/deploy.js production
deployBranch(process.argv[2] || "main");
Troubleshooting Common Issues
Build Failures
Issue: Build fails with memory errors
# Solution: Increase build resources
version: 1
frontend:
phases:
preBuild:
commands:
- npm ci
build:
commands:
# Increase memory for Node.js
- NODE_OPTIONS="--max-old-space-size=4096" npm run build
Issue: Module not found errors
# Solution: Clear cache and reinstall
rm -rf node_modules package-lock.json
npm install
Routing Issues
Issue: 404 errors on client-side routes
# Solution: Add proper redirects
redirects:
- source: "/<*>"
target: "/index.html"
status: "404-200"
Environment Variable Problems
Issue: Environment variables not available
// Check variable naming
// ❌ Wrong - not accessible in browser
const apiUrl = process.env.API_URL;
// ✅ Correct - accessible in browser
const apiUrl = process.env.NEXT_PUBLIC_API_URL;
Best Practices and Tips
Security Best Practices
// next.config.js - Security headers
const securityHeaders = [
{
key: "X-DNS-Prefetch-Control",
value: "on",
},
{
key: "Strict-Transport-Security",
value: "max-age=63072000; includeSubDomains; preload",
},
{
key: "X-XSS-Protection",
value: "1; mode=block",
},
{
key: "X-Frame-Options",
value: "SAMEORIGIN",
},
{
key: "X-Content-Type-Options",
value: "nosniff",
},
{
key: "Referrer-Policy",
value: "origin-when-cross-origin",
},
];
module.exports = {
async headers() {
return [
{
source: "/(.*)",
headers: securityHeaders,
},
];
},
};
Cost Optimization
// Strategies for cost optimization
const CostOptimizationTips = {
caching: "Implement aggressive caching to reduce compute costs",
bundleSize: "Optimize bundle size to reduce transfer costs",
images: "Use Amplify's image optimization",
staticGeneration: "Use SSG where possible to reduce Lambda invocations",
monitoring: "Monitor usage and set up billing alerts",
};
Performance Tips
- Bundle Analysis:
# Add to package.json
"scripts": {
"analyze": "ANALYZE=true npm run build",
"build": "next build"
}
# Install bundle analyzer
npm install --save-dev @next/bundle-analyzer
- Image Optimization:
// Use Next.js Image component
import Image from "next/image";
const OptimizedImage = () => (
<Image
src="/hero-image.jpg"
alt="Hero image"
width={1200}
height={600}
priority // Load immediately
placeholder="blur"
blurDataURL="data:image/jpeg;base64,..."
/>
);
Conclusion
AWS Amplify provides a powerful, scalable platform for deploying Next.js applications with minimal configuration and maximum performance. By following this comprehensive guide, you now have:
- Production-ready deployment pipeline with CI/CD
- Multi-environment setup with branch-based deployments
- Performance optimizations for global scale
- Security configurations following best practices
- Monitoring and analytics for continuous improvement
The combination of Next.js and AWS Amplify offers an excellent developer experience while providing enterprise-grade reliability and performance. Whether you're building a simple static site or a complex full-stack application, this deployment approach scales with your needs.
Key Takeaways
- Start simple with Amplify Console, advance to CLI for complex needs
- Use environment variables effectively for different deployment stages
- Implement proper caching strategies for optimal performance
- Monitor your applications to identify optimization opportunities
- Follow security best practices from day one
Next Steps
- Explore Amplify Studio for visual development
- Integrate Amplify Auth for user authentication
- Add Amplify DataStore for offline-first applications
- Implement Amplify Analytics for detailed insights
- Set up automated testing in your CI/CD pipeline
Start deploying your Next.js applications to AWS Amplify today and experience the power of modern cloud deployment!
Happy coding and deploying! 🚀