Deployment & Optimization

Deploy to Vercel, optimize images, fonts, and improve Core Web Vitals

Deploying Next.js

Next.js can be deployed to any hosting platform that supports Node.js, or as a static site. Vercel (the creators of Next.js) offers the most seamless deployment experience with zero configuration.

🚀 Deploy to Vercel

# Install Vercel CLI
npm i -g vercel

# Deploy (from project root)
vercel

# Deploy to production
vercel --prod

# Or connect to Git for automatic deployments
# 1. Push to GitHub/GitLab/Bitbucket
# 2. Import project at vercel.com/new
# 3. Every push deploys automatically!

Image Optimization

import Image from 'next/image';

// Basic usage
export default function Hero() {
  return (
    <Image
      src="/hero.jpg"
      alt="Hero image"
      width={1200}
      height={600}
      priority // Load immediately (above the fold)
    />
  );
}

// Responsive images
function ResponsiveImage() {
  return (
    <Image
      src="/photo.jpg"
      alt="Photo"
      fill // Fill parent container
      sizes="(max-width: 768px) 100vw, 50vw"
      style={{ objectFit: 'cover' }}
    />
  );
}

// External images (configure in next.config.js)
// next.config.js
const nextConfig = {
  images: {
    remotePatterns: [
      {
        protocol: 'https',
        hostname: 'images.unsplash.com',
      },
      {
        protocol: 'https',
        hostname: '*.amazonaws.com',
      },
    ],
  },
};

// Placeholder blur
<Image
  src="/large-image.jpg"
  alt="Large image"
  width={800}
  height={400}
  placeholder="blur"
  blurDataURL="data:image/jpeg;base64,..." // Or use static import
/>

Font Optimization

// app/layout.tsx
import { Inter, Roboto_Mono } from 'next/font/google';

// Load Google Fonts
const inter = Inter({
  subsets: ['latin'],
  display: 'swap',
  variable: '--font-inter',
});

const robotoMono = Roboto_Mono({
  subsets: ['latin'],
  display: 'swap',
  variable: '--font-roboto-mono',
});

export default function RootLayout({
  children,
}: {
  children: React.ReactNode;
}) {
  return (
    <html 
      lang="en" 
      className={`${inter.variable} ${robotoMono.variable}`}
    >
      <body className={inter.className}>{children}</body>
    </html>
  );
}

// Use in Tailwind CSS
// tailwind.config.js
module.exports = {
  theme: {
    extend: {
      fontFamily: {
        sans: ['var(--font-inter)'],
        mono: ['var(--font-roboto-mono)'],
      },
    },
  },
};

// Local fonts
import localFont from 'next/font/local';

const myFont = localFont({
  src: './fonts/MyFont.woff2',
  display: 'swap',
});

Metadata & SEO

// app/layout.tsx - Global metadata
import { Metadata } from 'next';

export const metadata: Metadata = {
  title: {
    template: '%s | My Site',
    default: 'My Site',
  },
  description: 'Welcome to my website',
  metadataBase: new URL('https://mysite.com'),
  openGraph: {
    title: 'My Site',
    description: 'Welcome to my website',
    images: ['/og-image.jpg'],
  },
  twitter: {
    card: 'summary_large_image',
  },
  robots: {
    index: true,
    follow: true,
  },
};

// app/blog/[slug]/page.tsx - Dynamic metadata
import { Metadata } from 'next';

interface Props {
  params: Promise<{ slug: string }>;
}

export async function generateMetadata({ params }: Props): Promise<Metadata> {
  const { slug } = await params;
  const post = await getPost(slug);
  
  return {
    title: post.title,
    description: post.excerpt,
    openGraph: {
      title: post.title,
      description: post.excerpt,
      images: [post.image],
    },
  };
}

// JSON-LD structured data
export default function Page() {
  const jsonLd = {
    '@context': 'https://schema.org',
    '@type': 'Article',
    headline: 'My Article',
    author: { '@type': 'Person', name: 'John Doe' },
  };
  
  return (
    <>
      <script
        type="application/ld+json"
        dangerouslySetInnerHTML={{ __html: JSON.stringify(jsonLd) }}
      />
      <article>...</article>
    </>
  );
}

Performance Optimization

// next.config.js
const nextConfig = {
  // Bundle analyzer
  experimental: {
    optimizePackageImports: ['lucide-react', '@heroicons/react'],
  },
  
  // Compression
  compress: true,
  
  // Production source maps (optional)
  productionBrowserSourceMaps: false,
};

// Dynamic imports for code splitting
import dynamic from 'next/dynamic';

const HeavyComponent = dynamic(() => import('./HeavyComponent'), {
  loading: () => <p>Loading...</p>,
  ssr: false, // Client-side only
});

// Lazy load below-the-fold content
const Comments = dynamic(() => import('./Comments'));

export default function Post() {
  return (
    <article>
      <h1>Post Title</h1>
      <p>Content...</p>
      
      {/* Only loads when scrolled into view */}
      <Suspense fallback={<CommentsSkeleton />}>
        <Comments />
      </Suspense>
    </article>
  );
}

Sitemap & Robots

// app/sitemap.ts
import { MetadataRoute } from 'next';

export default async function sitemap(): Promise<MetadataRoute.Sitemap> {
  const posts = await getPosts();
  
  const blogUrls = posts.map(post => ({
    url: `https://mysite.com/blog/${post.slug}`,
    lastModified: post.updatedAt,
    changeFrequency: 'weekly' as const,
    priority: 0.8,
  }));
  
  return [
    {
      url: 'https://mysite.com',
      lastModified: new Date(),
      changeFrequency: 'yearly',
      priority: 1,
    },
    {
      url: 'https://mysite.com/about',
      lastModified: new Date(),
      changeFrequency: 'monthly',
      priority: 0.5,
    },
    ...blogUrls,
  ];
}

// app/robots.ts
import { MetadataRoute } from 'next';

export default function robots(): MetadataRoute.Robots {
  return {
    rules: [
      {
        userAgent: '*',
        allow: '/',
        disallow: ['/admin/', '/api/'],
      },
    ],
    sitemap: 'https://mysite.com/sitemap.xml',
  };
}

📖 Deployment Documentation →

Other Hosting Options

# Docker deployment
# Dockerfile
FROM node:18-alpine AS builder
WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY . .
RUN npm run build

FROM node:18-alpine AS runner
WORKDIR /app
COPY --from=builder /app/.next/standalone ./
COPY --from=builder /app/.next/static ./.next/static
COPY --from=builder /app/public ./public

EXPOSE 3000
CMD ["node", "server.js"]

# Static export (no server features)
# next.config.js
const nextConfig = {
  output: 'export', // Generates static HTML
};

# Build and deploy to any static host
npm run build
# Upload 'out' folder to Netlify, GitHub Pages, etc.

✅ Deployment Checklist

  • ☑️ Use Image component for all images
  • ☑️ Optimize fonts with next/font
  • ☑️ Add metadata to all pages
  • ☑️ Generate sitemap and robots.txt
  • ☑️ Enable compression
  • ☑️ Use dynamic imports for large components
  • ☑️ Test Core Web Vitals with Lighthouse
  • ☑️ Set up environment variables