Skip to main content

Vercel vs Netlify vs Cloudflare Pages: Deployment Platforms (2026)

·PkgPulse Team

TL;DR

Vercel is the platform built for Next.js — zero-config deployment, edge/serverless functions, image optimization, analytics, the gold standard for React/Next.js apps. Netlify is the Jamstack platform — git-connected deploys, serverless functions, edge functions, forms, identity, split testing, framework-agnostic. Cloudflare Pages is the edge-first platform — global edge deployment, Workers integration, zero cold starts, generous free tier, built on Cloudflare's network. In 2026: Vercel for Next.js and React frameworks, Netlify for Jamstack and static sites, Cloudflare Pages for edge-first applications.

Key Takeaways

  • Vercel: Best for Next.js — automatic optimization, ISR, RSC, middleware
  • Netlify: Best for Jamstack — framework-agnostic, built-in forms/identity
  • Cloudflare Pages: Best value — generous free tier, zero cold starts, Workers
  • All three deploy from git with preview deployments
  • Vercel has the tightest Next.js integration (they created Next.js)
  • Cloudflare Pages has the most generous free tier

Vercel

Vercel — platform for frontend frameworks:

Deploy Next.js

# Connect repo and deploy:
npx vercel

# Or via git push (auto-deploy):
# 1. Connect GitHub repo in Vercel dashboard
# 2. Push to main → production deploy
# 3. Push to branch → preview deploy

vercel.json configuration

{
  "framework": "nextjs",
  "buildCommand": "next build",
  "outputDirectory": ".next",
  "regions": ["iad1", "sfo1"],
  "env": {
    "DATABASE_URL": "@database-url"
  },
  "headers": [
    {
      "source": "/api/(.*)",
      "headers": [
        { "key": "Access-Control-Allow-Origin", "value": "*" }
      ]
    }
  ],
  "redirects": [
    { "source": "/old-blog/:slug", "destination": "/blog/:slug", "permanent": true }
  ],
  "rewrites": [
    { "source": "/api/:path*", "destination": "https://api.pkgpulse.com/:path*" }
  ]
}

Serverless functions

// api/packages/[name].ts
import type { VercelRequest, VercelResponse } from "@vercel/node"

export default async function handler(
  req: VercelRequest,
  res: VercelResponse
) {
  const { name } = req.query

  const data = await fetch(`https://registry.npmjs.org/${name}`)
  const pkg = await data.json()

  res.setHeader("Cache-Control", "s-maxage=3600, stale-while-revalidate")
  res.json({
    name: pkg.name,
    version: pkg["dist-tags"].latest,
    description: pkg.description,
  })
}

Edge functions

// middleware.ts (Next.js middleware runs on Vercel Edge):
import { NextResponse } from "next/server"
import type { NextRequest } from "next/server"

export function middleware(request: NextRequest) {
  // Geo-based routing:
  const country = request.geo?.country || "US"

  if (country === "DE") {
    return NextResponse.rewrite(new URL("/de" + request.nextUrl.pathname, request.url))
  }

  // A/B testing:
  const bucket = request.cookies.get("ab-bucket")?.value || (Math.random() < 0.5 ? "a" : "b")

  const response = NextResponse.next()
  response.cookies.set("ab-bucket", bucket)
  response.headers.set("x-ab-bucket", bucket)

  return response
}

export const config = { matcher: ["/((?!_next|api|favicon).*)"] }

Key features

Vercel highlights:

  ✅ Zero-config Next.js deployment
  ✅ Automatic HTTPS and CDN
  ✅ Preview deployments per branch/PR
  ✅ Edge Middleware (global, zero cold start)
  ✅ Serverless Functions (Node.js, Go, Python, Ruby)
  ✅ Image Optimization (next/image)
  ✅ ISR (Incremental Static Regeneration)
  ✅ Analytics and Speed Insights
  ✅ Vercel KV, Postgres, Blob storage
  ✅ Domain management

Netlify

Netlify — Jamstack platform:

Deploy

# CLI deploy:
npx netlify deploy --prod

# Or connect GitHub for auto-deploy
# netlify.toml for configuration:

netlify.toml configuration

[build]
  command = "npm run build"
  publish = "dist"

[build.environment]
  NODE_VERSION = "20"

# Redirects:
[[redirects]]
  from = "/api/*"
  to = "/.netlify/functions/:splat"
  status = 200

[[redirects]]
  from = "/old-blog/*"
  to = "/blog/:splat"
  status = 301

# Headers:
[[headers]]
  for = "/api/*"
  [headers.values]
    Access-Control-Allow-Origin = "*"
    Cache-Control = "public, max-age=3600"

# Functions:
[functions]
  directory = "netlify/functions"
  node_bundler = "esbuild"

Serverless functions

// netlify/functions/packages.ts
import type { Handler, HandlerEvent } from "@netlify/functions"

export const handler: Handler = async (event: HandlerEvent) => {
  const name = event.queryStringParameters?.name

  if (!name) {
    return { statusCode: 400, body: JSON.stringify({ error: "Name required" }) }
  }

  const data = await fetch(`https://registry.npmjs.org/${name}`)
  const pkg = await data.json()

  return {
    statusCode: 200,
    headers: {
      "Content-Type": "application/json",
      "Cache-Control": "public, max-age=3600",
    },
    body: JSON.stringify({
      name: pkg.name,
      version: pkg["dist-tags"].latest,
    }),
  }
}

Edge functions

// netlify/edge-functions/geolocation.ts
import type { Context } from "@netlify/edge-functions"

export default async function handler(request: Request, context: Context) {
  const country = context.geo.country?.code || "US"

  // Geo-based content:
  if (country === "DE") {
    const url = new URL(request.url)
    url.pathname = "/de" + url.pathname
    return context.rewrite(url)
  }

  return context.next()
}

export const config = { path: "/*" }

Built-in features

Netlify highlights:

  ✅ Git-connected deploys (auto PR previews)
  ✅ Netlify Forms (no backend needed)
  ✅ Netlify Identity (auth without code)
  ✅ Split Testing (A/B by branch)
  ✅ Edge Functions (Deno-based)
  ✅ Serverless Functions (Node.js)
  ✅ Build plugins ecosystem
  ✅ Large Media (Git LFS)
  ✅ Analytics
  ✅ Framework-agnostic (Astro, SvelteKit, Next.js, etc.)

Cloudflare Pages

Cloudflare Pages — edge-first platform:

Deploy

# CLI deploy:
npx wrangler pages deploy ./dist

# Or connect GitHub for auto-deploy
# wrangler.toml for configuration:

wrangler.toml configuration

name = "pkgpulse"
compatibility_date = "2026-03-09"
pages_build_output_dir = "./dist"

# Bindings (access Cloudflare services):
[[kv_namespaces]]
binding = "CACHE"
id = "abc123..."

[[d1_databases]]
binding = "DB"
database_name = "pkgpulse"
database_id = "def456..."

[[r2_buckets]]
binding = "ASSETS"
bucket_name = "pkgpulse-assets"

[vars]
ENVIRONMENT = "production"

Functions (file-based routing)

// functions/api/packages/[name].ts
interface Env {
  DB: D1Database
  CACHE: KVNamespace
}

export const onRequestGet: PagesFunction<Env> = async (context) => {
  const name = context.params.name as string

  // Check cache:
  const cached = await context.env.CACHE.get(`pkg:${name}`)
  if (cached) {
    return new Response(cached, {
      headers: { "Content-Type": "application/json" },
    })
  }

  // Fetch from registry:
  const data = await fetch(`https://registry.npmjs.org/${name}`)
  const pkg = await data.json()

  const result = JSON.stringify({
    name: pkg.name,
    version: pkg["dist-tags"].latest,
  })

  // Cache for 1 hour:
  await context.env.CACHE.put(`pkg:${name}`, result, { expirationTtl: 3600 })

  return new Response(result, {
    headers: { "Content-Type": "application/json" },
  })
}

Full-stack with D1 and R2

// functions/api/reports.ts
interface Env {
  DB: D1Database
  ASSETS: R2Bucket
}

export const onRequestPost: PagesFunction<Env> = async (context) => {
  const body = await context.request.json()

  // Write to D1 (SQLite at the edge):
  await context.env.DB.prepare(
    "INSERT INTO reports (name, data, created_at) VALUES (?, ?, ?)"
  ).bind(body.name, JSON.stringify(body.data), new Date().toISOString()).run()

  // Store file in R2:
  await context.env.ASSETS.put(
    `reports/${body.name}.json`,
    JSON.stringify(body.data),
    { httpMetadata: { contentType: "application/json" } }
  )

  return new Response(JSON.stringify({ success: true }), {
    headers: { "Content-Type": "application/json" },
  })
}

Key features

Cloudflare Pages highlights:

  ✅ Global edge deployment (300+ locations)
  ✅ Zero cold starts (V8 isolates, not containers)
  ✅ Generous free tier (unlimited sites, 500 builds/month)
  ✅ Workers integration (full Cloudflare platform)
  ✅ D1 (edge SQLite), R2 (object storage), KV
  ✅ Preview deployments per branch
  ✅ Custom domains and automatic HTTPS
  ✅ Framework support (Next.js, Astro, SvelteKit, Remix)
  ✅ Web Analytics (free, privacy-first)
  ✅ DDoS protection included

Feature Comparison

FeatureVercelNetlifyCloudflare Pages
Best forNext.jsJamstackEdge-first apps
Framework supportAll (Next.js best)AllAll
Edge functions✅ (Middleware)✅ (Deno-based)✅ (Workers)
Serverless functions✅ (file-based)
Cold starts~250ms~250ms~0ms (V8 isolates)
Global CDN✅ (300+ PoPs)
Preview deploys
Built-in DBVercel Postgres, KVD1, KV
Object storageVercel BlobR2
Forms✅ (built-in)
Identity/auth✅ (built-in)
Image optimization✅ (next/image)✅ (Netlify Image CDN)✅ (Images)
Analytics✅ (paid)✅ (paid)✅ (free)
DDoS protection✅ (Cloudflare)
Free tier100GB bandwidth100GB bandwidthUnlimited bandwidth
PricingPer-seat + usagePer-seat + usageUsage-based

When to Use Each

Use Vercel if:

  • Building with Next.js (best-in-class support)
  • Want zero-config deployment with automatic optimizations
  • Need ISR, RSC, and advanced Next.js features
  • Want integrated Postgres, KV, and Blob storage

Use Netlify if:

  • Building Jamstack or static sites with any framework
  • Want built-in forms and identity (no backend needed)
  • Need split testing by branch (A/B testing)
  • Prefer a framework-agnostic platform

Use Cloudflare Pages if:

  • Want zero cold starts and true edge computing
  • Need the most generous free tier (unlimited bandwidth)
  • Want D1 (edge SQLite) and R2 (object storage)
  • Building on the Cloudflare ecosystem (Workers, KV, Queues)

Methodology

Feature comparison based on Vercel, Netlify, and Cloudflare Pages platforms and pricing as of March 2026.

Compare deployment and developer tooling on PkgPulse →

Comments

Stay Updated

Get the latest package insights, npm trends, and tooling tips delivered to your inbox.