ข้ามไปเนื้อหาหลัก

Category: guide

Deploying Static Sites — Vercel, Netlify, GitHub Pages

วิธี deploy static site ด้วย Vercel, Netlify, และ GitHub Pages — เปรียบเทียบ features, ราคา, และ workflow

· อ่านประมาณ 3 นาที

สารบัญ

เปรียบเทียบ Platforms

FeatureVercelNetlifyGitHub Pages
ราคา Free100GB bandwidth100GB bandwidthไม่จำกัด
Custom domain
HTTPS อัตโนมัติ
Branch preview✓ ทุก PR✓ ทุก PR
Edge Functions
Build cache✓ ดีมาก
ใช้งานง่าย★★★★★★★★★☆★★★☆☆

Vercel

Deploy อัตโนมัติจาก Git

# 1. Install Vercel CLI
npm i -g vercel

# 2. Login
vercel login

# 3. Deploy (ครั้งแรก)
vercel

# 4. Deploy production
vercel --prod

หรือเชื่อม GitHub แล้ว push → deploy อัตโนมัติ

vercel.json สำหรับ Static Site

{
  "buildCommand": "npm run build",
  "outputDirectory": "dist",
  "installCommand": "npm install",
  "framework": null
}

สำหรับ Astro โดยเฉพาะ:

{
  "framework": "astro"
}

Environment Variables

# ผ่าน CLI
vercel env add MY_API_KEY production

# หรือ Dashboard → Settings → Environment Variables

Redirect Rules

{
  "redirects": [
    { "source": "/old-path", "destination": "/new-path", "permanent": true },
    { "source": "/blog/:slug", "destination": "/posts/:slug", "permanent": false }
  ]
}

Netlify

Deploy ผ่าน CLI

npm i -g netlify-cli
netlify login
netlify init
netlify deploy --dir=dist
netlify deploy --dir=dist --prod

netlify.toml

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

[build.environment]
  NODE_VERSION = "20"

[[redirects]]
  from = "/old"
  to = "/new"
  status = 301

[[headers]]
  for = "/*"
  [headers.values]
    X-Frame-Options = "DENY"
    X-Content-Type-Options = "nosniff"
    Referrer-Policy = "strict-origin-when-cross-origin"

[[headers]]
  for = "/fonts/*"
  [headers.values]
    Cache-Control = "public, max-age=31536000, immutable"

Branch Deploys

[context.branch-deploy]
  command = "npm run build"

[context.deploy-preview]
  command = "npm run build"

GitHub Pages

GitHub Actions Workflow

# .github/workflows/deploy.yml
name: Deploy to GitHub Pages

on:
  push:
    branches: [main]
  workflow_dispatch:

permissions:
  contents: read
  pages: write
  id-token: write

concurrency:
  group: "pages"
  cancel-in-progress: false

jobs:
  build:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with:
          node-version: 20
          cache: npm
      - run: npm ci
      - run: npm run build
      - uses: actions/upload-pages-artifact@v3
        with:
          path: dist/

  deploy:
    needs: build
    runs-on: ubuntu-latest
    environment:
      name: github-pages
      url: ${{ steps.deployment.outputs.page_url }}
    steps:
      - uses: actions/deploy-pages@v4
        id: deployment

ตั้งค่า astro.config.mjs สำหรับ GitHub Pages

// ถ้า deploy ที่ username.github.io/repo-name (ไม่ใช่ custom domain)
export default defineConfig({
  site: 'https://username.github.io',
  base: '/repo-name',
});

Custom Domain

ตั้งค่า DNS

# A record (apex domain)
@    A    76.76.21.21    (Vercel)
@    A    75.2.60.5      (Netlify)
@    A    185.199.108.153 (GitHub Pages)
@    A    185.199.109.153
@    A    185.199.110.153
@    A    185.199.111.153

# CNAME record (www)
www  CNAME  cname.vercel-dns.com.   (Vercel)
www  CNAME  [site].netlify.app.     (Netlify)
www  CNAME  username.github.io.     (GitHub Pages)

Best Practices

Build Optimization

# ตรวจ bundle size
npm run build && du -sh dist/

# ดู output ละเอียด
npx astro build --verbose 2>&1 | grep -E 'dist|kB|bytes'

Cache Strategy

# netlify.toml
[[headers]]
  for = "/_astro/*"
  [headers.values]
    Cache-Control = "public, max-age=31536000, immutable"

[[headers]]
  for = "/fonts/*"
  [headers.values]
    Cache-Control = "public, max-age=31536000, immutable"

[[headers]]
  for = "/*.html"
  [headers.values]
    Cache-Control = "public, max-age=0, must-revalidate"

Astro build puts hashed assets ใน /_astro/ ทำให้ immutable cache ปลอดภัย

Security Headers

[[headers]]
  for = "/*"
  [headers.values]
    X-Frame-Options = "DENY"
    X-Content-Type-Options = "nosniff"
    Referrer-Policy = "strict-origin-when-cross-origin"
    Permissions-Policy = "camera=(), microphone=(), geolocation=()"
    Content-Security-Policy = """
      default-src 'self';
      script-src 'self' 'unsafe-inline';
      style-src 'self' 'unsafe-inline';
      img-src 'self' data: https:;
      font-src 'self';
    """