This comprehensive guide covers deployment options for the Architect Resume Portfolio across multiple platforms and hosting providers.
# Run all quality checks
npm run lint
npm run type-check
npm run test
npm run build
# Check bundle size
npm run analyze # If analyze script is available
# Create production build
npm run build
# Test production build locally
npm start
# Verify all pages load correctly
# Check console for any errors
# Test contact form functionality
# Verify responsive design on multiple devices
Create appropriate environment files for different deployment stages:
.env.local (Development)# Development environment
NEXT_PUBLIC_SITE_URL=http://localhost:3000
NEXT_PUBLIC_CONTACT_EMAIL=john@johnarchitect.com
NEXT_PUBLIC_GOOGLE_ANALYTICS_ID=
# Contact form (if using external service)
CONTACT_FORM_ENDPOINT=
CONTACT_FORM_API_KEY=
# Optional: Database configuration
DATABASE_URL=
.env.production (Production)# Production environment
NEXT_PUBLIC_SITE_URL=https://yourdomain.com
NEXT_PUBLIC_CONTACT_EMAIL=your-actual-email@domain.com
NEXT_PUBLIC_GOOGLE_ANALYTICS_ID=G-XXXXXXXXXX
# Contact form service
CONTACT_FORM_ENDPOINT=https://api.emailservice.com/send
CONTACT_FORM_API_KEY=your-api-key
# Content Delivery Network
NEXT_PUBLIC_CDN_URL=https://cdn.yourdomain.com
next.config.js Production Configuration/** @type {import('next').NextConfig} */
const nextConfig = {
// Image optimization
images: {
domains: [
'images.unsplash.com',
'res.cloudinary.com',
'yourdomain.com'
],
formats: ['image/webp', 'image/avif'],
},
// Experimental features
experimental: {
optimizeCss: true,
},
// Compression
compress: true,
// Security headers
headers: async () => [
{
source: '/(.*)',
headers: [
{
key: 'X-Frame-Options',
value: 'DENY',
},
{
key: 'X-Content-Type-Options',
value: 'nosniff',
},
{
key: 'Referrer-Policy',
value: 'origin-when-cross-origin',
},
],
},
],
// Redirects
redirects: async () => [
{
source: '/home',
destination: '/',
permanent: true,
},
],
}
module.exports = nextConfig
Vercel is the recommended deployment platform for Next.js applications.
# Push your code to GitHub/GitLab/Bitbucket
git add .
git commit -m "feat: prepare for deployment"
git push origin main
# Install Vercel CLI
npm install -g vercel
# Login to Vercel
vercel login
# Deploy (first time)
vercel
# Deploy to production
vercel --prod
# Set environment variables
vercel env add NEXT_PUBLIC_SITE_URL production
vercel env add CONTACT_FORM_API_KEY production
Create vercel.json for advanced configuration:
{
"framework": "nextjs",
"buildCommand": "npm run build",
"devCommand": "npm run dev",
"installCommand": "npm install",
"functions": {
"app/api/contact.ts": {
"maxDuration": 30
}
},
"headers": [
{
"source": "/images/(.*)",
"headers": [
{
"key": "Cache-Control",
"value": "public, max-age=31536000, immutable"
}
]
}
],
"rewrites": [
{
"source": "/sitemap.xml",
"destination": "/api/sitemap"
}
]
}
Type: CNAME
Name: www
Value: cname.vercel-dns.com
Type: A
Name: @
Value: 76.76.19.61
# netlify.toml
[build]
publish = ".next"
command = "npm run build"
[build.environment]
NODE_VERSION = "18"
[[headers]]
for = "/_next/static/*"
[headers.values]
Cache-Control = "public, max-age=31536000, immutable"
[[headers]]
for = "/images/*"
[headers.values]
Cache-Control = "public, max-age=31536000"
[[redirects]]
from = "/home"
to = "/"
status = 301
# Install Netlify CLI
npm install -g netlify-cli
# Login
netlify login
# Build and deploy
npm run build
netlify deploy --dir=.next --prod
<!-- Contact form with Netlify handling -->
<form name="contact" method="POST" data-netlify="true">
<input type="hidden" name="form-name" value="contact" />
<!-- form fields -->
</form>
# Install Amplify CLI
npm install -g @aws-amplify/cli
# Configure Amplify
amplify configure
# Initialize project
amplify init
# Add hosting
amplify add hosting
# Deploy
amplify publish
# amplify.yml
version: 1
frontend:
phases:
preBuild:
commands:
- npm ci
build:
commands:
- npm run build
artifacts:
baseDirectory: .next
files:
- '**/*'
cache:
paths:
- node_modules/**/*
- .next/cache/**/*
Note: Next.js 15 has deprecated next export. For static hosting, consider using:
// next.config.js
module.exports = {
output: 'export',
// ... other config
}
# Build static files
npm run build
# Create S3 bucket
aws s3 mb s3://your-portfolio-bucket
# Enable static website hosting
aws s3 website s3://your-portfolio-bucket \
--index-document index.html \
--error-document 404.html
# Upload files (from the out/ directory if using static export)
aws s3 sync out/ s3://your-portfolio-bucket --delete
{
"CallerReference": "portfolio-distribution",
"DefaultRootObject": "index.html",
"Origins": [
{
"Id": "S3-your-portfolio-bucket",
"DomainName": "your-portfolio-bucket.s3.amazonaws.com",
"S3OriginConfig": {
"OriginAccessIdentity": ""
}
}
],
"DefaultCacheBehavior": {
"TargetOriginId": "S3-your-portfolio-bucket",
"ViewerProtocolPolicy": "redirect-to-https",
"Compress": true,
"ForwardedValues": {
"QueryString": false,
"Cookies": {
"Forward": "none"
}
}
}
}
# Connect to EC2 instance
ssh -i your-key.pem ubuntu@your-ec2-ip
# Install Node.js
curl -fsSL https://deb.nodesource.com/setup_18.x | sudo -E bash -
sudo apt-get install -y nodejs
# Install PM2
sudo npm install -g pm2
# Clone repository
git clone https://github.com/yourusername/architect-resume.git
cd architect-resume
# Install dependencies
npm install
# Build application
npm run build
# Start with PM2
pm2 start npm --name "portfolio" -- start
pm2 startup
pm2 save
# /etc/nginx/sites-available/portfolio
server {
listen 80;
server_name yourdomain.com;
location / {
proxy_pass http://localhost:3000;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection 'upgrade';
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_cache_bypass $http_upgrade;
}
}
# Update system
sudo apt update && sudo apt upgrade -y
# Install Node.js 18
curl -fsSL https://deb.nodesource.com/setup_18.x | sudo -E bash -
sudo apt-get install -y nodejs
# Install additional tools
sudo apt install -y git nginx certbot python3-certbot-nginx
# Create application user
sudo adduser portfolio
sudo usermod -aG sudo portfolio
# Switch to application user
sudo su - portfolio
# Clone and setup application
git clone https://github.com/yourusername/architect-resume.git
cd architect-resume
npm install
npm run build
# Install PM2 globally
sudo npm install -g pm2
# Create PM2 ecosystem file
cat > ecosystem.config.js << EOF
module.exports = {
apps: [{
name: 'architect-portfolio',
script: 'npm',
args: 'start',
cwd: '/home/portfolio/architect-resume',
env: {
NODE_ENV: 'production',
PORT: 3000
}
}]
}
EOF
# Start application
pm2 start ecosystem.config.js
pm2 startup
pm2 save
# Obtain SSL certificate
sudo certbot --nginx -d yourdomain.com -d www.yourdomain.com
# Auto-renewal
sudo crontab -e
# Add: 0 12 * * * /usr/bin/certbot renew --quiet
For shared hosting providers that support Node.js:
npm run build and npm start// next.config.js - Add static export configuration
module.exports = {
output: 'export',
}
# Build static files
npm run build
out/ folder to your hosting provider# Multi-stage Dockerfile for production
FROM node:18-alpine AS deps
RUN apk add --no-cache libc6-compat
WORKDIR /app
COPY package.json package-lock.json ./
RUN npm ci --only=production
FROM node:18-alpine AS builder
WORKDIR /app
COPY --from=deps /app/node_modules ./node_modules
COPY . .
# Build application
RUN npm run build
FROM node:18-alpine AS runner
WORKDIR /app
ENV NODE_ENV production
RUN addgroup --system --gid 1001 nodejs
RUN adduser --system --uid 1001 nextjs
COPY --from=builder /app/public ./public
COPY --from=builder /app/package.json ./package.json
# Automatically leverage output traces to reduce image size
COPY --from=builder --chown=nextjs:nodejs /app/.next/standalone ./
COPY --from=builder --chown=nextjs:nodejs /app/.next/static ./.next/static
USER nextjs
EXPOSE 3000
ENV PORT 3000
CMD ["node", "server.js"]
# docker-compose.yml
version: '3.8'
services:
portfolio:
build: .
ports:
- "3000:3000"
environment:
- NODE_ENV=production
- NEXT_PUBLIC_SITE_URL=https://yourdomain.com
restart: unless-stopped
labels:
- "traefik.enable=true"
- "traefik.http.routers.portfolio.rule=Host(`yourdomain.com`)"
- "traefik.http.routers.portfolio.tls.certresolver=letsencrypt"
traefik:
image: traefik:v2.8
command:
- --api.dashboard=true
- --providers.docker=true
- --entrypoints.web.address=:80
- --entrypoints.websecure.address=:443
- --certificatesresolvers.letsencrypt.acme.email=your-email@domain.com
- --certificatesresolvers.letsencrypt.acme.storage=/acme.json
- --certificatesresolvers.letsencrypt.acme.httpchallenge.entrypoint=web
ports:
- "80:80"
- "443:443"
volumes:
- /var/run/docker.sock:/var/run/docker.sock
- ./acme.json:/acme.json
restart: unless-stopped
# k8s-deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: architect-portfolio
spec:
replicas: 3
selector:
matchLabels:
app: architect-portfolio
template:
metadata:
labels:
app: architect-portfolio
spec:
containers:
- name: portfolio
image: your-registry/architect-portfolio:latest
ports:
- containerPort: 3000
env:
- name: NODE_ENV
value: "production"
- name: NEXT_PUBLIC_SITE_URL
value: "https://yourdomain.com"
---
apiVersion: v1
kind: Service
metadata:
name: architect-portfolio-service
spec:
selector:
app: architect-portfolio
ports:
- port: 80
targetPort: 3000
type: LoadBalancer
# .github/workflows/deploy.yml
name: Deploy to Production
on:
push:
branches: [main]
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Setup Node.js
uses: actions/setup-node@v3
with:
node-version: '18'
cache: 'npm'
- name: Install dependencies
run: npm ci
- name: Run tests
run: npm test
- name: Run lint
run: npm run lint
- name: Type check
run: npm run type-check
- name: Build
run: npm run build
deploy-vercel:
needs: test
runs-on: ubuntu-latest
if: github.ref == 'refs/heads/main'
steps:
- uses: actions/checkout@v3
- name: Deploy to Vercel
uses: amondnet/vercel-action@v20
with:
vercel-token: $
vercel-org-id: $
vercel-project-id: $
vercel-args: '--prod'
deploy-aws:
needs: test
runs-on: ubuntu-latest
if: github.ref == 'refs/heads/main'
steps:
- uses: actions/checkout@v3
- name: Configure AWS credentials
uses: aws-actions/configure-aws-credentials@v1
with:
aws-access-key-id: $
aws-secret-access-key: $
aws-region: us-east-1
- name: Setup Node.js
uses: actions/setup-node@v3
with:
node-version: '18'
cache: 'npm'
- name: Install dependencies
run: npm ci
- name: Build
run: npm run build
- name: Deploy to S3
run: aws s3 sync out/ s3://$ --delete
- name: Invalidate CloudFront
run: aws cloudfront create-invalidation --distribution-id $ --paths "/*"
# .gitlab-ci.yml
stages:
- test
- build
- deploy
variables:
NODE_VERSION: "18"
test:
stage: test
image: node:$NODE_VERSION
cache:
paths:
- node_modules/
script:
- npm ci
- npm run test
- npm run lint
- npm run type-check
- npm run build
deploy_production:
stage: deploy
image: node:$NODE_VERSION
only:
- main
script:
- npm ci
- npm run build
# Deploy to your hosting provider
artifacts:
paths:
- out/
expire_in: 1 week
// next.config.js optimizations
module.exports = {
experimental: {
optimizeCss: true,
optimizePackageImports: ['lucide-react', 'framer-motion'],
},
// Bundle analyzer
webpack: (config, { isServer }) => {
if (process.env.ANALYZE) {
const { BundleAnalyzerPlugin } = require('webpack-bundle-analyzer')
config.plugins.push(
new BundleAnalyzerPlugin({
analyzerMode: 'static',
openAnalyzer: false,
})
)
}
return config
},
}
// Optimize images for production
import Image from 'next/image'
const OptimizedImage = ({ src, alt, ...props }) => (
<Image
src={src}
alt={alt}
quality={85}
placeholder="blur"
blurDataURL="data:image/jpeg;base64,/9j/4AAQSkZJRgABAQAAAQABAAD/2wBDAAYEBQYFBAYGBQYHBwYIChAKCgkJChQODwwQFxQYGBcUFhYaHSUfGhsjHBYWICwgIyYnKSopGR8tMC0oMCUoKSj/2wBDAQcHBwoIChMKChMoGhYaKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCj/wAARCAABAAEDASIAAhEBAxEB/8QAFQABAQAAAAAAAAAAAAAAAAAAAAv/xAAhEAACAQMDBQAAAAAAAAAAAAABAgMABAUGIWGRkbHB0eH/xAAUAQEAAAAAAAAAAAAAAAAAAAAA/8QAFBEBAAAAAAAAAAAAAAAAAAAAAP/aAAwDAQACEQMRAD8A2p5hJV1qDGVaOwMUo6wUBP/Z"
sizes="(max-width: 768px) 100vw, (max-width: 1200px) 50vw, 33vw"
{...props}
/>
)
// Service Worker for caching (if using PWA)
const CACHE_NAME = 'architect-portfolio-v1'
const urlsToCache = [
'/',
'/static/js/bundle.js',
'/static/css/main.css',
'/images/hero-bg.jpg'
]
self.addEventListener('install', (event) => {
event.waitUntil(
caches.open(CACHE_NAME)
.then((cache) => cache.addAll(urlsToCache))
)
})
// lib/gtag.ts
export const GA_TRACKING_ID = process.env.NEXT_PUBLIC_GA_ID
export const pageview = (url: string) => {
window.gtag('config', GA_TRACKING_ID, {
page_location: url,
})
}
export const event = ({ action, category, label, value }: {
action: string
category: string
label?: string
value?: number
}) => {
window.gtag('event', action, {
event_category: category,
event_label: label,
value: value,
})
}
// lib/sentry.ts
import * as Sentry from '@sentry/nextjs'
Sentry.init({
dsn: process.env.NEXT_PUBLIC_SENTRY_DSN,
environment: process.env.NODE_ENV,
tracesSampleRate: 1.0,
})
// lib/performance.ts
export const reportWebVitals = (metric: any) => {
if (metric.label === 'web-vital') {
// Log to analytics service
window.gtag('event', metric.name, {
event_category: 'Web Vitals',
event_label: metric.id,
value: Math.round(metric.value),
non_interaction: true,
})
}
}
Set up monitoring with services like:
# Clear cache and rebuild
rm -rf .next node_modules package-lock.json
npm install
npm run build
NEXT_PUBLIC_ for client-side accessnext.config.js# Analyze bundle size
npm run build
npx @next/bundle-analyzer
# Check for large dependencies
npm ls --depth=0
// next.config.js
headers: async () => [
{
source: '/(.*)',
headers: [
{
key: 'X-Frame-Options',
value: 'DENY',
},
{
key: 'Content-Security-Policy',
value: "default-src 'self'; script-src 'self' 'unsafe-eval' 'unsafe-inline'; style-src 'self' 'unsafe-inline';"
}
],
},
]
# Check for vulnerabilities
npm audit
# Fix issues
npm audit fix
# Update dependencies
npm update
This deployment guide provides comprehensive instructions for deploying your Architect Resume Portfolio across multiple platforms. Choose the deployment method that best fits your needs and technical requirements.