Next.js course Β· Module 8: Deployment and Production
Deployment on Vercel, Netlify, and other platforms
In this lesson8
After creating a Next.js application, a key stage is deploying it to the production environment. In this module, we will look at various hosting platforms that offer an optimized environment for Next.js applications, with a particular focus on Vercel - the platform created by the Next.js authors.
Deployment on Vercel
Vercel is a platform that offers the best native support for Next.js applications, as both technologies are developed by the same company. This ensures optimal support for all Next.js features, automated deployments, and a global CDN.
Project preparation
Before deploying, ensure that your project is ready for production:
- Checking environment variables - ensure that all environment variables are properly configured
- Initial optimization - run
next buildlocally to check for potential errors - Version control - ensure that your project is in a Git repository (GitHub, GitLab or Bitbucket)
Step by step: Deployment on Vercel
Create an account on Vercel
- Go to vercel.com and sign up, preferably using a GitHub/GitLab/Bitbucket account
Import the project
- After logging in, click "Add New..." > "Project"
- Select your repository from the list
Deployment configuration
- Vercel automatically detects that the project is a Next.js application
- You can customize the project name, deployment branch, and environment variables
- For standard Next.js projects, the default settings are usually sufficient
Deployment
- Click "Deploy"
- Vercel will build and deploy your application, optimizing it for production
Deployment verification
- After a successful deployment, you will receive a unique URL
- Check your application and ensure that everything works correctly
Custom domain configuration
To configure a custom domain for your project on Vercel:
- Go to the project panel
- Select the tab "Domains"
- Click "Add" and enter your domain name
- Follow the instructions to configure the DNS records:
- For root domains (e.g., example.com): Create an A record pointing to Vercel's IP
- For subdomains (e.g., www.example.com): Create a CNAME record pointing to the Vercel domain
Automatic deployments and previews
One of Vercel's greatest strengths is the automatic deployment system:
Automatic production deployments
- Each push to the main branch (e.g.,
main) triggers an automatic deployment
- Each push to the main branch (e.g.,
Preview Deployments
- Each pull request automatically creates a unique preview environment
- This allows testing changes before merging with the main branch
1// Example of Next.js configuration optimized for Vercel
2// next.config.js
3
4module.exports = {
5 // Image optimization - automatically handled by Vercel Image Optimization
6 images: {
7 domains: ['example.com', 'images.unsplash.com'],
8 },
9
10 // Integrated Vercel analytics
11 vercelAnalytics: {
12 enabled: true,
13 },
14
15 // Internationalization settings
16 i18n: {
17 locales: ['pl', 'en', 'de'],
18 defaultLocale: 'pl',
19 },
20};Advanced Vercel features
Vercel offers a range of advanced features worth using:
- Edge Functions - running code close to the user on Vercel's global edge network
- Serverless Functions - automatic conversion of API Routes into serverless functions
- Edge Middleware - running middleware at the network edge
- Edge Config - storing configuration at the edge with ultra-fast reads
- Analytics - built-in analytics tools
- Speed Insights - monitoring Core Web Vitals
Deployment on Netlify
Netlify is another popular platform that offers excellent support for Next.js applications, especially after the introduction of the Netlify Next.js Runtime feature.
Project preparation for Netlify
- Create the netlify.toml configuration file in the project's main directory:
1# netlify.toml
2[build]
3 command = "npm run build"
4 publish = ".next"
5
6[[plugins]]
7 package = "@netlify/plugin-nextjs"- Install the Next.js plugin for Netlify:
1npm install -D @netlify/plugin-nextjs
2# or
3yarn add -D @netlify/plugin-nextjsStep by step: Deployment on Netlify
Create an account on Netlify
- Go to netlify.com and sign up
Import the project
- Click "Add new site" > "Import an existing project"
- Connect to your Git provider and select your repository
Deployment configuration
- Netlify should automatically detect the Next.js project settings
- Make sure the build command is
npm run buildoryarn build - The publish directory should be set to
.next
Configure environment variables
- Go to "Site settings" > "Build & deploy" > "Environment"
- Add all needed environment variables
Deployment
- Click "Deploy site"
Netlify-specific features
Netlify offers a range of unique features that can be useful in Next.js projects:
- Netlify Functions - the equivalent of API Routes
- Netlify Edge Functions - similar to Next.js Middleware, but running at the edge
- Netlify Forms - built-in form handling without the need to create API endpoints
- Netlify Identity - user authentication service
1// Example of using Netlify Functions with Next.js
2// /netlify/functions/hello-world.js
3
4exports.handler = async function(event, context) {
5 return {
6 statusCode: 200,
7 body: JSON.stringify({ message: "Hello World" }),
8 headers: {
9 'Content-Type': 'application/json'
10 }
11 };
12};
13
14// Access from the Next.js application
15// pages/api-example.js
16import { useState, useEffect } from 'react';
17
18export default function ApiExample() {
19 const [data, setData] = useState(null);
20
21 useEffect(() => {
22 fetch('/.netlify/functions/hello-world')
23 .then(res => res.json())
24 .then(data => setData(data));
25 }, []);
26
27 return (
28 <div>
29 <h1>API Example</h1>
30 <pre>{JSON.stringify(data, null, 2)}</pre>
31 </div>
32 );
33}Deployment on AWS Amplify
AWS Amplify is a service from Amazon that also offers good support for Next.js applications, especially if you already use the AWS ecosystem.
Project preparation for AWS Amplify
- Create the amplify.yml file in the project's main directory:
1# amplify.yml
2version: 1
3frontend:
4 phases:
5 preBuild:
6 commands:
7 - npm ci
8 build:
9 commands:
10 - npm run build
11 artifacts:
12 baseDirectory: .next
13 files:
14 - '**/*'
15 cache:
16 paths:
17 - node_modules/**/*Step by step: Deployment on AWS Amplify
Create an AWS account or log in to an existing one
Go to the AWS Amplify console
- Select "Host web app"
Connect the repository
- Select a repository provider (GitHub, GitLab, BitBucket, or AWS CodeCommit repository)
- Log in and select your repository
Build settings configuration
- Amplify should automatically detect the Next.js framework
- You can adjust or confirm the build settings
Configure environment variables
- Add all needed environment variables in the "Environment variables" section
Deployment
- Click "Save and deploy"
Integration with AWS services
One of the main strengths of AWS Amplify is easy integration with other AWS services:
1// Example: Integration with AWS Lambda and DynamoDB
2// pages/api/users.js
3
4import { DynamoDB } from 'aws-sdk';
5
6const dynamoDB = new DynamoDB.DocumentClient();
7
8export default async function handler(req, res) {
9 if (req.method === 'GET') {
10 const params = {
11 TableName: process.env.DYNAMODB_TABLE_NAME,
12 };
13
14 try {
15 const data = await dynamoDB.scan(params).promise();
16 res.status(200).json(data.Items);
17 } catch (error) {
18 res.status(500).json({ error: 'Failed to fetch users' });
19 }
20 }
21}Deployment on Digital Ocean App Platform
Digital Ocean App Platform is another hosting option that offers good support for Next.js applications.
Step by step: Deploying on Digital Ocean
Create a Digital Ocean account or log in to an existing one
Go to Digital Ocean App Platform
- Click "Create App"
Select the code source
- Connect your GitHub, GitLab repository, or choose another source
Application configuration
- Select the branch to deploy
- Select the application type as "Web Service"
- In the build settings, select "Next.js" as the example application
Adjust the settings
- Set the build command to
npm run buildoryarn build - Set the run command to
npm startoryarn start
- Set the build command to
Finalization
- Select a pricing plan
- Click "Launch App"
Other hosting options for Next.js
GitHub Pages with GitHub Actions
For statically generated Next.js applications, you can use GitHub Pages with GitHub Actions:
- Create a GitHub Actions workflow file:
1# .github/workflows/deploy.yml
2name: Deploy to GitHub Pages
3
4on:
5 push:
6 branches: [ main ]
7
8jobs:
9 build:
10 runs-on: ubuntu-latest
11 steps:
12 - uses: actions/checkout@v2
13 - uses: actions/setup-node@v2
14 with:
15 node-version: '18'
16 - name: Install dependencies
17 run: npm ci
18 - name: Build and export
19 run: npm run build && npm run export
20 - name: Deploy
21 uses: peaceiris/actions-gh-pages@v3
22 with:
23 github_token: ${{ secrets.GITHUB_TOKEN }}
24 publish_dir: ./out- Adjust next.config.js:
1// next.config.js
2module.exports = {
3 output: 'export',
4 basePath: '/repository-name', // If the page is not on the main domain
5 images: {
6 unoptimized: true, // For GitHub Pages
7 },
8};- Add a script to package.json:
1{
2 "scripts": {
3 "export": "next export"
4 }
5}Firebase Hosting
Firebase Hosting is another option, especially if you use other Firebase services:
- Install the Firebase CLI:
1npm install -g firebase-tools- Initialize the Firebase project:
1firebase login
2firebase init hosting- Configure the firebase.json file:
1{
2 "hosting": {
3 "public": "out",
4 "ignore": ["firebase.json", "**/.*", "**/node_modules/**"],
5 "rewrites": [
6 {
7 "source": "**",
8 "destination": "/index.html"
9 }
10 ]
11 }
12}- Build and deploy the application:
1npm run build
2npm run export
3firebase deploySelf-hosting on your own server
If you prefer to host the application on your own server:
Node.js server
- Prepare the start script in package.json:
1{
2 "scripts": {
3 "start": "next start -p $PORT"
4 }
5}- Use PM2 to manage the process:
1npm install -g pm2
2pm2 start npm --name "next-app" -- start- Configuring Nginx as a reverse proxy:
1server {
2 listen 80;
3 server_name yourpage.com www.yourpage.com;
4
5 location / {
6 proxy_pass http://localhost:3000;
7 proxy_http_version 1.1;
8 proxy_set_header Upgrade $http_upgrade;
9 proxy_set_header Connection 'upgrade';
10 proxy_set_header Host $host;
11 proxy_cache_bypass $http_upgrade;
12 }
13}Containerization with Docker
- Create a Dockerfile:
1# Dockerfile
2FROM node:18-alpine AS base
3
4# Dependency installation stage
5FROM base AS deps
6WORKDIR /app
7COPY package.json yarn.lock* package-lock.json* pnpm-lock.yaml* ./
8RUN if [ -f yarn.lock ]; then yarn --frozen-lockfile; elif [ -f package-lock.json ]; then npm ci; elif [ -f pnpm-lock.yaml ]; then yarn global add pnpm && pnpm i; else echo "No lockfile present" && exit 1; fi
9
10# Build stage
11FROM base AS builder
12WORKDIR /app
13COPY /app/node_modules ./node_modules
14COPY . .
15RUN npm run build
16
17# Production stage
18FROM base AS runner
19WORKDIR /app
20ENV NODE_ENV production
21
22RUN addgroup --system --gid 1001 nodejs
23RUN adduser --system --uid 1001 nextjs
24
25COPY /app/public ./public
26COPY /app/.next/standalone ./
27COPY /app/.next/static ./.next/static
28
29USER nextjs
30EXPOSE 3000
31ENV PORT 3000
32
33CMD ["node", "server.js"]- Configure next.config.js:
1// next.config.js
2module.exports = {
3 output: 'standalone',
4};- Build and run the container:
1docker build -t next-app .
2docker run -p 3000:3000 next-appBest practices
Environment variables
Manage environment variables according to Next.js best practices:
Distinguish between client and server variables:
- Variables that should be available in the browser should have the prefix
NEXT_PUBLIC_ - The remaining variables are available only in server code
- Variables that should be available in the browser should have the prefix
Environment files:
.env.local- variables for the local environment, not committed to the repository.env.development- variables for the development environment.env.production- variables for the production environment.env.test- variables for the test environment
Deployment optimization
Cache and CDN:
- Configure appropriate cache headers for static resources
- Use a CDN to speed up the delivery of static resources
Compression:
- Enable Gzip or Brotli compression on the server
Core Web Vitals:
- Monitor Web Vitals metrics after deployment
- Use tools like Lighthouse or PageSpeed Insights
1// next.config.js - production optimization
2module.exports = {
3 // Compression
4 compress: true,
5
6 // Image optimization
7 images: {
8 domains: ['images.example.com'],
9 formats: ['image/avif', 'image/webp'],
10 },
11
12 // Static generator - maximum performance for ISR/SSG pages
13 staticPageGenerationTimeout: 120,
14
15 // Production optimization
16 productionBrowserSourceMaps: false, // Disable source maps in production
17
18 // webpack configuration
19 webpack: (config, { dev, isServer }) => {
20 // webpack optimizations...
21 return config;
22 },
23};Deployment automation
Consider implementing Continuous Deployment (CD) to automate the deployment process:
1# GitHub Actions - deploy.yml
2name: Deploy
3
4on:
5 push:
6 branches: [ main ]
7
8jobs:
9 deploy:
10 runs-on: ubuntu-latest
11 steps:
12 - uses: actions/checkout@v2
13
14 - name: Setup Node.js
15 uses: actions/setup-node@v2
16 with:
17 node-version: '18'
18
19 - name: Install dependencies
20 run: npm ci
21
22 - name: Run tests
23 run: npm test
24
25 - name: Build project
26 run: npm run build
27
28 - name: Deploy to production
29 uses: amondnet/vercel-action@v20
30 with:
31 vercel-token: ${{ secrets.VERCEL_TOKEN }}
32 vercel-org-id: ${{ secrets.ORG_ID }}
33 vercel-project-id: ${{ secrets.PROJECT_ID }}
34 vercel-args: '--prod'Summary
Next.js application deployment can be done in many ways, depending on the project's needs and the team's preferences:
- Vercel - the best native support, optimized for Next.js
- Netlify - strong competitor with good Next.js support and additional features
- AWS Amplify - good choice for users of the AWS ecosystem
- Digital Ocean App Platform - simple PaaS platform with good Next.js support
- Self-hosting - ability to have full control over the infrastructure
The choice of hosting platform should be tailored to:
- The specifics of the project and its technical requirements
- Budget and expected costs
- The team's experience with the given platform
- Needs related to scalability and performance
- Additional services and integrations that may be needed
Regardless of the chosen platform, it's worth following best practices for managing environments, environment variables, security, and deployment automation.
Code for this lesson: App.tsx
1import React, { useState } from 'react';
2
3// Demo: Deploying to Vercel, Netlify and other platforms
4interface Platform {
5 id: string;
6 name: string;
7 icon: string;
8 color: string;
9 features: string[];
10 pricing: string;
11 deployCommand: string;
12 envSetup: string;
13 pros: string[];
14 cons: string[];
15}
16
17const platforms: Platform[] = [
18 {
19 id: 'vercel', name: 'Vercel', icon: 'V', color: '#000',
20 features: ['Edge Functions', 'ISR', 'Analytics', 'Preview Deploys', 'Serverless'],
21 pricing: 'Free tier / $20/mo Pro',
22 deployCommand: 'vercel deploy --prod',
23 envSetup: 'vercel env add DATABASE_URL\nvercel env add NEXTAUTH_SECRET',
24 pros: ['Native Next.js support', 'Automatic CI/CD', 'Edge Network', 'Preview on every PR'],
25 cons: ['Vendor lock-in', 'Costs at scale', 'Serverless limits'],
26 },
27 {
28 id: 'netlify', name: 'Netlify', icon: 'N', color: '#00ad9f',
29 features: ['Edge Functions', 'Forms', 'Identity', 'Split Testing', 'Plugins'],
30 pricing: 'Free tier / $19/mo Pro',
31 deployCommand: 'netlify deploy --prod',
32 envSetup: 'netlify env:set DATABASE_URL "value"\nnetlify env:set NEXTAUTH_SECRET "value"',
33 pros: ['Easy configuration', 'Built-in forms', 'Split testing', 'Plugin ecosystem'],
34 cons: ['Slower SSR', 'Limited Next.js features', 'Build limits'],
35 },
36 {
37 id: 'docker', name: 'Docker / VPS', icon: 'D', color: '#2496ed',
38 features: ['Full control', 'Custom runtime', 'Scaling', 'No limits'],
39 pricing: 'VPS from $5/mo',
40 deployCommand: 'docker build -t myapp .\ndocker run -p 3000:3000 myapp',
41 envSetup: '# .env.production\nDATABASE_URL=postgresql://...\nNEXTAUTH_SECRET=...',
42 pros: ['Full control', 'No vendor lock-in', 'Lower costs at scale', 'Custom configuration'],
43 cons: ['More work', 'DevOps knowledge required', 'No auto-scaling', 'Self-managed monitoring'],
44 },
45 {
46 id: 'aws', name: 'AWS Amplify', icon: 'A', color: '#ff9900',
47 features: ['CloudFront CDN', 'Lambda@Edge', 'DynamoDB', 'S3', 'CI/CD'],
48 pricing: 'Pay-as-you-go',
49 deployCommand: 'amplify push\namplify publish',
50 envSetup: 'amplify add env staging\n# Configure in Amplify Console',
51 pros: ['AWS ecosystem', 'Scaling', 'Multiple regions', 'Integration with other AWS services'],
52 cons: ['Complexity', 'Learning curve', 'Costs hard to predict'],
53 },
54];
55
56type DeployStep = 'idle' | 'building' | 'testing' | 'deploying' | 'done';
57
58function DeploymentDemo() {
59 const [selectedPlatform, setSelectedPlatform] = useState<Platform>(platforms[0]);
60 const [deployStep, setDeployStep] = useState<DeployStep>('idle');
61 const [showComparison, setShowComparison] = useState(false);
62
63 const simulateDeploy = async () => {
64 setDeployStep('building');
65 await new Promise(r => setTimeout(r, 1500));
66 setDeployStep('testing');
67 await new Promise(r => setTimeout(r, 1000));
68 setDeployStep('deploying');
69 await new Promise(r => setTimeout(r, 2000));
70 setDeployStep('done');
71 };
72
73 const stepLabels: Record<DeployStep, string> = {
74 idle: 'Ready to deploy',
75 building: 'Building application...',
76 testing: 'Running tests...',
77 deploying: 'Deploying to ' + selectedPlatform.name + '...',
78 done: 'Deployed successfully!',
79 };
80
81 const stepColors: Record<DeployStep, string> = {
82 idle: '#8892b0', building: '#f59e0b', testing: '#3b82f6', deploying: '#8b5cf6', done: '#10b981',
83 };
84
85 return (
86 <div style={{ background: '#0f0f23', minHeight: '100vh', padding: '20px', color: '#e0e0e0', fontFamily: 'system-ui' }}>
87 <h1 style={{ color: '#64ffda', marginBottom: '8px' }}>Deployment Platforms</h1>
88 <p style={{ color: '#8892b0', marginBottom: '24px' }}>Deploying a Next.js application to various platforms</p>
89
90 <div style={{ display: 'flex', gap: '12px', marginBottom: '24px', flexWrap: 'wrap' }}>
91 {platforms.map(p => (
92 <button key={p.id} onClick={() => { setSelectedPlatform(p); setDeployStep('idle'); }} style={{
93 padding: '14px 20px', borderRadius: '12px', cursor: 'pointer', border: '2px solid',
94 borderColor: selectedPlatform.id === p.id ? '#64ffda' : '#333',
95 background: selectedPlatform.id === p.id ? '#1a2a3e' : '#1a1a2e', color: '#fff',
96 }}>
97 <div style={{ fontSize: '20px', fontWeight: 'bold', color: '#64ffda' }}>{p.icon}</div>
98 <div style={{ fontSize: '14px' }}>{p.name}</div>
99 </button>
100 ))}
101 <button onClick={() => setShowComparison(!showComparison)} style={{ padding: '14px 20px', borderRadius: '12px', background: showComparison ? '#7c3aed' : '#1a1a2e', color: '#fff', border: '1px solid #333', cursor: 'pointer', marginLeft: 'auto' }}>
102 Compare
103 </button>
104 </div>
105
106 {!showComparison ? (
107 <div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: '20px' }}>
108 <div>
109 <div style={{ background: '#1a1a2e', padding: '20px', borderRadius: '12px', border: '1px solid #333', marginBottom: '16px' }}>
110 <h3 style={{ color: '#64ffda', marginTop: 0 }}>{selectedPlatform.name}</h3>
111 <p style={{ color: '#8892b0', fontSize: '13px' }}>Pricing: {selectedPlatform.pricing}</p>
112 <div style={{ display: 'flex', flexWrap: 'wrap', gap: '6px', marginBottom: '12px' }}>
113 {selectedPlatform.features.map(f => (
114 <span key={f} style={{ fontSize: '11px', padding: '3px 8px', borderRadius: '4px', background: '#333', color: '#8892b0' }}>{f}</span>
115 ))}
116 </div>
117 <div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: '12px' }}>
118 <div>
119 <strong style={{ color: '#10b981', fontSize: '13px' }}>Advantages:</strong>
120 <ul style={{ fontSize: '12px', paddingLeft: '16px', color: '#8892b0', margin: '4px 0' }}>
121 {selectedPlatform.pros.map((p, i) => <li key={i}>{p}</li>)}
122 </ul>
123 </div>
124 <div>
125 <strong style={{ color: '#ef4444', fontSize: '13px' }}>Cons:</strong>
126 <ul style={{ fontSize: '12px', paddingLeft: '16px', color: '#8892b0', margin: '4px 0' }}>
127 {selectedPlatform.cons.map((c, i) => <li key={i}>{c}</li>)}
128 </ul>
129 </div>
130 </div>
131 </div>
132
133 <div style={{ background: '#1a1a2e', padding: '16px', borderRadius: '8px', border: '1px solid #333' }}>
134 <strong style={{ color: '#f59e0b', fontSize: '13px' }}>Deploy command:</strong>
135 <pre style={{ fontSize: '12px', color: '#64ffda', margin: '8px 0 0', whiteSpace: 'pre-wrap' }}>{selectedPlatform.deployCommand}</pre>
136 </div>
137 </div>
138
139 <div>
140 <div style={{ background: '#1a1a2e', padding: '20px', borderRadius: '12px', border: '1px solid #333' }}>
141 <h3 style={{ color: '#64ffda', marginTop: 0, fontSize: '16px' }}>Deploy simulation</h3>
142 <div style={{ marginBottom: '16px', padding: '12px', borderRadius: '8px', background: stepColors[deployStep] + '20', border: '1px solid ' + stepColors[deployStep] }}>
143 <span style={{ color: stepColors[deployStep] }}>{stepLabels[deployStep]}</span>
144 </div>
145 <button onClick={simulateDeploy} disabled={deployStep !== 'idle' && deployStep !== 'done'} style={{ padding: '10px 20px', borderRadius: '8px', background: '#64ffda', color: '#0f0f23', border: 'none', cursor: 'pointer', fontWeight: 'bold' }}>
146 Deploy
147 </button>
148 </div>
149 </div>
150 </div>
151 ) : (
152 <div style={{ overflowX: 'auto' }}>
153 <table style={{ width: '100%', borderCollapse: 'collapse', fontSize: '13px' }}>
154 <thead>
155 <tr>{['', ...platforms.map(p => p.name)].map((h, i) => <th key={i} style={{ padding: '10px', textAlign: 'left', borderBottom: '1px solid #333', color: '#64ffda' }}>{h}</th>)}</tr>
156 </thead>
157 <tbody>
158 {['pricing', 'features'].map(field => (
159 <tr key={field}>
160 <td style={{ padding: '10px', borderBottom: '1px solid #222', color: '#8892b0' }}>{field}</td>
161 {platforms.map(p => (
162 <td key={p.id} style={{ padding: '10px', borderBottom: '1px solid #222', color: '#e0e0e0' }}>
163 {field === 'pricing' ? p.pricing : p.features.slice(0, 3).join(', ')}
164 </td>
165 ))}
166 </tr>
167 ))}
168 </tbody>
169 </table>
170 </div>
171 )}
172 </div>
173 );
174}
175
176export default DeploymentDemo;Spotted a mistake in this lesson?
Check yourself
Answer the questions from this lesson. Pick an answer to see right away whether it is correct.
1. Vercel is:
2. Netlify offers:
These are 2 of 5 questions for this lesson. Solve the rest in the game.
Hands-on tasks in the game
- Click in order
Arrange the syntax of the FROM instruction in a Dockerfile
- Code editor
Create a Dockerfile for a Next.js app with a multi-stage build: install the dependencies and build the app in a stage based on a node image, copy only the build output into the final stage (COPY --from), set NODE_ENV=production, run the app as a non-root user (USER) and start it with CMD.
- Horizontal ordering
Arrange the elements in the correct order for the build and production start command:
- Click in order
Arrange the syntax for configuring the bundle analyzer in Next.js
- Click in order
Click in the correct order to arrange the Vercel CLI deploy command: