본문으로 건너뛰기 Complete Cloudflare Pages Complete Guide | Free Deployment

Complete Cloudflare Pages Complete Guide | Free Deployment

Complete Cloudflare Pages Complete Guide | Free Deployment

이 글의 핵심

Covers deploying static sites and SSR apps for free with Cloudflare Pages, and running server logic with Edge Functions. From GitHub integration, Wrangler CLI, environment variables, custom domains to build optimization with practical examples.

Introduction

Cloudflare Pages is a platform for deploying static sites and Server Side Rendering (SSR) apps to a global Edge network. Similar to Vercel/Netlify, but strengths include unlimited free bandwidth, 300+ city CDN, and integration with Workers, D1, R2. This article covers GitHub repository integration, Wrangler CLI deployment, environment variables, custom domains, Functions (Edge server logic), build optimization, and comparison with Vercel through practical examples. For overall Node.js app deployment, see Node.js Deployment Guide (PM2, Docker, AWS, Nginx), and for CI/CD pipeline, see Node.js + GitHub Actions CI/CD.

Reality in Practice

When learning development, everything seems clean and theoretical. But practice is different. You wrestle with legacy code, chase tight deadlines, and face unexpected bugs. The content covered in this article was initially learned as theory, but it was through applying it to actual projects that I realized “Ah, this is why it’s designed this way.” What stands out in my memory is the trial and error from my first project. I did everything by the book but couldn’t figure out why it wasn’t working, spending days struggling. Eventually, through a senior developer’s code review, I discovered the problem and learned a lot in the process. In this article, I’ll cover not just theory but also the pitfalls you might encounter in practice and how to solve them.

Table of Contents

  1. What is Cloudflare Pages?
  2. GitHub Integration Deployment
  3. Wrangler CLI Deployment
  4. Environment Variables and Secrets
  5. Custom Domain Setup
  6. Functions (Edge Server Logic)
  7. Build Optimization
  8. Vercel/Netlify Comparison
  9. Summary

1. What is Cloudflare Pages?

Core Features

ItemDescription
CDN300+ cities worldwide, Cloudflare network
Free Plan500 builds/month, unlimited requests/bandwidth
Supported FrameworksReact, Vue, Astro, Next.js, SvelteKit, Remix, etc.
SSR/EdgeCloudflare Workers-based Functions
Build EnvironmentNode.js, Python, Ruby, Go, etc.

When to Use

  • Static Sites: Blogs, documentation, landing pages
  • JAMstack: Astro, Hugo, Jekyll, Eleventy
  • SSR Apps: Next.js App Router, SvelteKit, Remix
  • Edge API: Cloudflare Workers + D1(SQLite) + R2(S3 compatible) If traffic is global, bandwidth cost is a concern, or you want to run server logic on Edge, Cloudflare Pages is a strong choice.

2. GitHub Integration Deployment

The simplest method is connecting GitHub repository from Cloudflare dashboard.

2-1. Basic Setup

  1. Cloudflare DashboardPagesCreate a project
  2. Connect to Git → Connect GitHub account
  3. Select repository → Branch (main or production)
  4. Build settings:
    • Framework preset: Auto-detect Astro, Next.js, React, etc.
    • Build command: npm run build
    • Build output directory: dist (Astro), out (Next.js), .next (Next.js SSR)
  5. Save and Deploy

2-2. Automatic Deployment

  • Push to main branch → Automatic build and deploy
  • Preview deployment created for each PR (URL: <branch>.<project>.pages.dev)
  • Build logs viewable in real-time from dashboard Pros: Easy setup, automatic PR preview.
    Cons: Consumes Cloudflare build environment (500 free/month), limited build time/cache control.

3. Wrangler CLI Deployment

Wrangler is Cloudflare’s official CLI, allowing local build → upload to save Cloudflare build count.

3-1. Install Wrangler

npm install -g wrangler
# Or project local
npm install --save-dev wrangler

3-2. Authentication

wrangler login

Authenticate Cloudflare account in browser.

3-3. Deploy

# Build locally
npm run build
# Upload dist folder to Cloudflare Pages
wrangler pages deploy dist --project-name=my-project

On first deployment, project is automatically created if it doesn’t exist.

3-4. Using Wrangler in GitHub Actions

name: Deploy to Cloudflare Pages
on:
  push:
    branches: [main]
jobs:
  deploy:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      
      - name: Setup Node.js
        uses: actions/setup-node@v4
        with:
          node-version: '20'
          cache: 'npm'
      
      - name: Install dependencies
        run: npm ci
      
      - name: Build
        run: npm run build
        env:
          NODE_OPTIONS: '--max-old-space-size=4096'
      
      - name: Deploy to Cloudflare Pages
        uses: cloudflare/wrangler-action@v3
        with:
          apiToken: ${{ secrets.CLOUDFLARE_API_TOKEN }}
          accountId: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
          command: pages deploy dist --project-name=my-project

Required secrets:

  • CLOUDFLARE_API_TOKEN: Dashboard → API TokensEdit Cloudflare Workers permission
  • CLOUDFLARE_ACCOUNT_ID: Check in dashboard URL (dash.cloudflare.com/<ACCOUNT_ID>/...) Pros: Full build environment control, free cache strategy, saves Cloudflare build count.

4. Environment Variables and Secrets

4-1. Set in Cloudflare Dashboard

Pages projectSettingsEnvironment variables

  • Production: Used for main branch deployment
  • Preview: Used for PR/branch deployment
DATABASE_URL=postgresql://...
API_KEY=abc123

Accessible during both build time (npm run build) and runtime (Functions).

4-2. Access in Code

Build Time (Node.js):

// astro.config.mjs, next.config.js, etc.
const apiKey = process.env.API_KEY;

Runtime (Cloudflare Functions):

// functions/api/data.js
export async function onRequest(context) {
  const apiKey = context.env.API_KEY;
  return new Response(JSON.stringify({ key: apiKey }));
}

4-3. Local Development

.dev.vars file (add to .gitignore):

DATABASE_URL=postgresql://localhost/dev
API_KEY=dev-key-123
wrangler pages dev dist --local

5. Custom Domain Setup

5-1. Add Domain

Pages projectCustom domainsSet up a custom domain

  1. Enter domain (e.g., blog.example.com)
  2. Add DNS record:
    • CNAME: blog<project>.pages.dev
    • Or A/AAAA: IP provided by Cloudflare
  3. Automatic SSL issuance (Let’s Encrypt, free)

5-2. Apex Domain (example.com)

If managing domain with Cloudflare:

  • CNAME flattening automatically applied
  • Add example.com<project>.pages.dev CNAME For other DNS providers, need to add A record with Cloudflare IP.

6. Functions (Edge Server Logic)

Cloudflare Pages Functions are Cloudflare Workers based, running server code on Edge.

6-1. Basic Structure

my-project/
├── functions/
│   ├── api/
│   │   ├── hello.js       # /api/hello
│   │   └── users/[id].js  # /api/users/:id
│   └── _middleware.js     # Applied to all requests
├── public/
└── dist/

6-2. Example: API Endpoint

// functions/api/hello.js
export async function onRequest(context) {
  const { request, env, params } = context;
  
  return new Response(
    JSON.stringify({ message: 'Hello from Edge!' }),
    { headers: { 'Content-Type': 'application/json' } }
  );
}

After deployment, access at https://my-project.pages.dev/api/hello.

6-3. Dynamic Routes

// functions/api/users/[id].js
export async function onRequest(context) {
  const userId = context.params.id;
  
  // D1 (Cloudflare SQLite) example
  const db = context.env.DB;
  const user = await db.prepare('SELECT * FROM users WHERE id = ?')
    .bind(userId)
    .first();
  
  return new Response(JSON.stringify(user), {
    headers: { 'Content-Type': 'application/json' }
  });
}

6-4. Middleware

// functions/_middleware.js
export async function onRequest(context) {
  const start = Date.now();
  
  // Execute next handler
  const response = await context.next();
  
  // Add response header
  response.headers.set('X-Response-Time', `${Date.now() - start}ms`);
  
  return response;
}

6-5. SSR Frameworks

Astro SSR:

// astro.config.mjs
import { defineConfig } from 'astro/config';
import cloudflare from '@astrojs/cloudflare';
export default defineConfig({
  output: 'server', // or 'hybrid'
  adapter: cloudflare()
});

Next.js:

npm install @cloudflare/next-on-pages
// next.config.js
module.exports = {
  experimental: {
    runtime: 'experimental-edge'
  }
};

7. Build Optimization

7-1. GitHub Actions Build Cache

- name: Cache dependencies
  uses: actions/cache@v4
  with:
    path: |
      node_modules
      .astro
      .next/cache
    key: ${{ runner.os }}-build-${{ hashFiles('package-lock.json') }}
    restore-keys: |
      ${{ runner.os }}-build-

7-2. Reduce Build Time

Parallel Processing:

// scripts/build.mjs
import { execSync } from 'child_process';
const tasks = [
  'node scripts/generate-og-images.mjs',
  'node scripts/generate-sitemap.mjs',
  'node scripts/generate-rss.mjs'
];
await Promise.all(tasks.map(cmd => 
  execSync(cmd, { stdio: 'inherit' })
));
execSync('astro build', { stdio: 'inherit' });

Incremental Build:

  • Astro: Cache .astro folder
  • Next.js: Cache .next/cache folder

7-3. Large Page Optimization

Problem: 1,000+ pages → 10+ minute build Solution:

  1. OG Image Cache: Regenerate only changed posts
  2. Static Pages First: output: 'static' or hybrid
  3. Parallel Rendering: Astro 4.0+ automatic parallelization
// astro.config.mjs
export default defineConfig({
  output: 'static',
  build: {
    concurrency: 8 // Parallel rendering
  }
});

8. Vercel/Netlify Comparison

8-1. Feature Comparison Table

ItemCloudflare PagesVercelNetlify
Free Builds500/month6,000 min/month300 min/month
Free BandwidthUnlimited100GB/month100GB/month
CDN Locations300+ cities100+ cities100+ cities
Edge FunctionsWorkers (V8)Edge Functions (V8)Edge Functions (Deno)
DB IntegrationD1 (SQLite), R2 (S3)Vercel Postgres, BlobNetlify Blobs
Build TimeMediumFast (Next.js optimized)Medium
DXGoodVery GoodGood

8-2. Selection Criteria

Choose Cloudflare Pages when:

  • High global traffic and bandwidth cost is a concern
  • Using Cloudflare ecosystem like Workers, D1, R2
  • Need unlimited bandwidth on free plan
  • Building blog with static generators like Astro/Hugo (Astro Blog Guide reference) Choose Vercel when:
  • Using Next.js App Router and prioritizing developer experience
  • Build time is important and many preview deployments
  • Using Vercel Analytics/Speed Insights Choose Netlify when:
  • Need Form processing, Identity(auth), Split Testing
  • Prefer Deno-based Edge Functions

9. Real Example: Astro Blog Deployment

9-1. Project Structure

my-blog/
├── src/
│   ├── pages/
│   ├── content/
│   └── components/
├── public/
├── functions/
│   └── api/
│       └── views.js  # View count API
├── astro.config.mjs
├── wrangler.toml
└── package.json

9-2. GitHub Actions Workflow

name: Deploy to Cloudflare Pages
on:
  push:
    branches: [main]
jobs:
  deploy:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      
      - name: Setup Node.js
        uses: actions/setup-node@v4
        with:
          node-version: '20'
          cache: 'npm'
      
      - name: Install dependencies
        run: npm ci
      
      - name: Validate frontmatter
        run: npm run validate
      
      - name: Build
        run: npm run build
        env:
          NODE_OPTIONS: '--max-old-space-size=4096'
      
      - name: Deploy to Cloudflare Pages
        uses: cloudflare/wrangler-action@v3
        with:
          apiToken: ${{ secrets.CLOUDFLARE_API_TOKEN }}
          accountId: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
          command: pages deploy dist --project-name=my-blog --commit-message="Deploy from GitHub Actions"

9-3. View Count API (Functions)

// functions/api/views.js
export async function onRequest(context) {
  const { request, env } = context;
  const url = new URL(request.url);
  const slug = url.searchParams.get('slug');
  
  if (!slug) {
    return new Response('Missing slug', { status: 400 });
  }
  
  // D1 (Cloudflare SQLite)
  const db = env.DB;
  
  if (request.method === 'POST') {
    // Increment view count
    await db.prepare('INSERT INTO views (slug, count) VALUES (?, 1) ON CONFLICT(slug) DO UPDATE SET count = count + 1')
      .bind(slug)
      .run();
  }
  
  // Query view count
  const result = await db.prepare('SELECT count FROM views WHERE slug = ?')
    .bind(slug)
    .first();
  
  return new Response(
    JSON.stringify({ slug, views: result?.count || 0 }),
    { headers: { 'Content-Type': 'application/json' } }
  );
}

D1 Binding (wrangler.toml):

name = "my-blog"
pages_build_output_dir = "dist"
[[d1_databases]]
binding = "DB"
database_name = "my-blog-db"
database_id = "abc123..."

10. Advanced Tips

10-1. Redirects

_redirects file (include in build output folder):

/old-url  /new-url  301
/blog/*   /posts/:splat  302

Or _headers:

/*
  X-Frame-Options: DENY
  X-Content-Type-Options: nosniff

10-2. Limit Preview Branches

# .github/workflows/deploy-cloudflare.yml
on:
  push:
    branches: [main]
  # Leave PR preview to Cloudflare automatic deployment

10-3. Build Failure Notification

- name: Notify on failure
  if: failure()
  run: |
    curl -X POST ${{ secrets.SLACK_WEBHOOK }} \
      -H 'Content-Type: application/json' \
      -d '{"text":"Cloudflare Pages deployment failed!"}'

10-4. Rollback

Dashboard:

  • Deployments → Select previous deployment → Rollback to this deployment CLI:
wrangler pages deployment list --project-name=my-blog
wrangler pages deployment rollback <deployment-id>

11. Summary

Key Summary

Cloudflare Pages Advantages:

  • Unlimited free bandwidth
  • 300+ city global CDN
  • Workers, D1, R2 integration
  • SSR/Edge Functions support Deployment Methods:
  1. GitHub Integration: Easiest, automatic preview
  2. Wrangler CLI: Build control, save count Recommended Configuration:
  • Local/CI: Build in GitHub Actions + Wrangler upload
  • Preview: Cloudflare automatic deployment
  • Environment Variables: Separate Production/Preview in dashboard
  • Monitoring: Cloudflare Analytics + Sentry

Checklist

Before Deployment:

  • Check build command (npm run build)
  • Check output directory (dist, out, .next)
  • Set environment variables (API keys, DB URL)
  • Add .env, .dev.vars to .gitignore After Deployment:
  • Check custom domain DNS propagation (up to 24 hours)
  • Check SSL certificate issuance
  • Test Functions operation
  • Check 404 page

Next Steps

Good articles to use with Cloudflare Pages:


Frequently Asked Questions (FAQ)

Q. When would I use this in practice?

A. Guide to deploying static sites and SSR apps with Cloudflare Pages.

Q. What should I read before this?

A. Follow the previous article or related articles links at the bottom of each post to learn in sequence.

Q. Where can I study this more deeply?

A. Check cppreference and the relevant library’s official documentation. The reference links at the end of the article are also worth using.


Other articles related to this topic.


Keywords Covered in This Article (Related Search Terms)

This article covers Cloudflare Pages, Cloudflare, Deployment, Edge Computing, Wrangler, GitHub Actions, CI/CD, DevOps, JAMstack.