Deploying to Cloudflare Pages: A Practical Walkthrough


I’ve deployed projects to Vercel, Netlify, Railway, and a few VPS setups over the years. They all have their strengths. But for static sites and simple full-stack apps, I keep coming back to Cloudflare Pages.

The free tier is genuinely generous - 500 builds per month, unlimited bandwidth, automatic preview deployments for pull requests, and a global CDN that’s fast everywhere. For side projects and small client work, it’s hard to beat.

Here’s a practical walkthrough of deploying a project to Cloudflare Pages, covering the things the official docs cover quickly and the things I had to figure out the hard way.

What Cloudflare Pages Can Deploy

Cloudflare Pages started as a static site host but has evolved significantly. It now supports:

  • Static sites: HTML, CSS, JS. Any static site generator (Astro, Hugo, 11ty, etc.)
  • SPAs: React, Vue, Svelte apps
  • Full-stack frameworks: Next.js, Nuxt, SvelteKit, Astro with SSR, Remix
  • API routes: Through Cloudflare Workers (built into Pages)

The full-stack support uses Cloudflare Workers under the hood, which runs your server-side code on Cloudflare’s edge network. The cold start times are effectively zero, which is a genuine advantage over traditional serverless platforms.

The easiest way to deploy is connecting a GitHub or GitLab repository.

  1. Go to dash.cloudflare.com and navigate to Workers & Pages.
  2. Click “Create” and select “Pages.”
  3. Connect your GitHub/GitLab account and select your repository.
  4. Configure your build settings:
    • Framework preset: Select your framework (Astro, Next.js, etc.). Cloudflare auto-detects most frameworks.
    • Build command: Usually npm run build or pnpm build.
    • Build output directory: Depends on your framework. For Astro it’s dist. For Next.js it’s .next. The preset usually sets this correctly.
  5. Click “Save and Deploy.”

That’s it. Cloudflare will build and deploy your project. Every subsequent push to your main branch triggers a new deployment. Pushes to other branches create preview deployments with unique URLs.

Method 2: Direct Upload

For projects you don’t want to connect to a Git repository, you can upload directly:

npm install -g wrangler
wrangler pages deploy ./dist --project-name=my-project

This uploads the contents of your build output directory. It’s useful for CI/CD pipelines where you want more control over the build process.

Environment Variables

This is where people (including me) get tripped up.

Cloudflare Pages has two types of environment variables:

Build-time variables: Available during the build process. Set these in the Cloudflare dashboard under Settings > Environment variables > Production/Preview. These are injected during the build step.

Runtime variables: Available to Workers/server-side code. These are set the same way but are accessed differently in your code - through the env parameter in your Worker/server functions, not through process.env.

For static sites, only build-time variables matter. For full-stack apps with server-side rendering, you need to handle both.

// In a Cloudflare Pages function (server-side)
export const onRequest = async (context) => {
  const apiKey = context.env.API_KEY; // Runtime variable
  // NOT process.env.API_KEY
};

Important: Preview deployments use separate environment variables from production. Set them independently. I’ve been bitten by this more than once - everything works in preview, deploy to production, and it breaks because the production environment variables weren’t set.

Custom Domains

Adding a custom domain is straightforward if your DNS is already on Cloudflare:

  1. Go to your Pages project > Custom domains > Set up a custom domain.
  2. Enter your domain name.
  3. Cloudflare automatically adds the DNS record.

If your DNS is elsewhere, you’ll need to add a CNAME record pointing to <project-name>.pages.dev. Then verify it in the Cloudflare dashboard.

SSL certificates are provisioned automatically. No configuration needed.

Build Configuration Gotchas

A few things I’ve run into:

Node.js version. Cloudflare Pages defaults to Node 18. If your project needs a different version, set the NODE_VERSION environment variable. For most modern projects, set it to 20 or 22.

Package manager. If you use pnpm, you might need to set NPM_FLAGS to --legacy-peer-deps or add a .npmrc file. Cloudflare Pages works best with npm out of the box but supports pnpm and yarn.

Build memory. Larger projects can hit memory limits during builds. If your build fails with an out-of-memory error, try reducing parallelism in your build process or upgrading to the paid plan.

Monorepo support. If your project is in a subdirectory of a monorepo, set the “Root directory” in your build configuration. Cloudflare will cd into that directory before running the build command.

Pages Functions (Server-Side)

Cloudflare Pages supports server-side functions through the functions directory. Drop a TypeScript or JavaScript file in functions/api/hello.ts and it becomes an API endpoint at /api/hello.

// functions/api/hello.ts
export const onRequestGet: PagesFunction = async (context) => {
  return new Response(JSON.stringify({ message: "Hello from the edge" }), {
    headers: { "Content-Type": "application/json" },
  });
};

Functions run on Cloudflare Workers, which means they execute at the edge - close to your users. Response times are typically under 50ms for simple operations.

You can use D1 (Cloudflare’s serverless SQLite) for database needs, KV for key-value storage, and R2 for object storage. The ecosystem is surprisingly complete for building full applications without a traditional backend.

Performance

One of the main reasons I use Cloudflare Pages is speed. Your static assets are served from Cloudflare’s global CDN with over 300 points of presence. Time to first byte is typically under 20ms for cached content.

For an Astro static site I deployed recently, Lighthouse scores are consistently 95-100 across all categories. The CDN does most of the heavy lifting - proper cache headers, Brotli compression, and HTTP/3 are all handled automatically.

The technology infrastructure behind platforms like Cloudflare has become incredibly sophisticated. Firms working in custom AI development and cloud infrastructure are pushing what’s possible with edge computing, and tools like Cloudflare Pages are the downstream benefit of that innovation.

When Not to Use Cloudflare Pages

Cloudflare Pages isn’t the right choice for everything:

  • Complex server-side applications with long-running processes, WebSocket connections, or heavy computation are better suited to traditional servers or containers.
  • Next.js with all features - some Next.js features (ISR, image optimisation) have limitations on Cloudflare compared to Vercel.
  • Projects requiring specific runtime features - Workers use V8 isolates, not Node.js. Some Node.js APIs aren’t available.

For static sites, SPAs, and simple full-stack applications, though, Cloudflare Pages is excellent. Fast, free for most use cases, and increasingly capable. Give it a try.