Next.js 블로그 만들기 — GitHub Pages에서 Cloudflare Pages로 이전하기

9 min read
Cloudflare배포SEO
Next.js 블로그 만들기 — GitHub Pages에서 Cloudflare Pages로 이전하기

GitHub Pages, 뭐가 부족했나

GitHub Pages로 블로그를 운영하면서 몇 가지 불편한 점이 있었다.

항목GitHub Pages제한
트래픽월 100GB 대역폭초과 시 사이트 다운
빌드GitHub Actions 의존분당 빌드 횟수 제한
basePath필수 (/repo-name)URL이 길어짐
커스텀 도메인별도 DNS 설정 필요Freenom 등 외부 서비스 의존
CDN없음글로벌 성능 불리

특히 basePath 문제가 가장 귀찮았다. terajh.github.io/tera-log처럼 레포 이름이 경로에 붙기 때문에, 모든 정적 파일 경로에 /tera-log를 prefix로 달아야 했다. 이미지, favicon, JSON 파일 전부.

next.config.mjs (GitHub Pages 시절)
const nextConfig = {
  output: 'export',
  basePath: '/tera-log', // 이게 모든 경로에 붙는다
  images: { unoptimized: true },
}

커스텀 도메인을 연결하면 basePath를 제거할 수 있지만, 무료 도메인 서비스(Freenom)는 2023년부터 신규 등록이 막혔고, 유료 도메인은 연간 비용이 든다.

Cloudflare Pages라는 대안

Cloudflare Pages는 정적 사이트 호스팅 서비스다. GitHub Pages와 같은 역할이지만, 제한이 훨씬 적다.

항목GitHub PagesCloudflare Pages
대역폭월 100GB무제한
빌드월 2,000분 (Actions)월 500회
도메인username.github.io/repoproject.pages.dev
basePath필수 (Organization Pages 제외)불필요
CDNX글로벌 CDN 기본 제공
DDoS 보호X기본 제공
HTTPSLet's Encrypt자동
빌드 환경GitHub Actions 직접 구성자동 감지
비용무료무료

가장 큰 장점은 basePath가 필요 없다는 것이다. tera-log.pages.dev처럼 프로젝트 이름이 서브도메인으로 들어가기 때문에, 사이트가 루트(/)에서 바로 서빙된다.

이전 과정

1. Cloudflare Pages 프로젝트 생성

  1. Cloudflare Dashboard 접속
  2. Workers & Pages > Pages > Create a project
  3. Connect to Git > GitHub 로그인 > 레포 선택

2. 빌드 설정

설정
Framework presetNone
Build commandpnpm run build
Build output directoryout

Next.js의 output: 'export'out/ 디렉토리에 정적 파일을 생성하기 때문에, 이걸 그대로 배포 경로로 지정하면 된다.

Save and Deploy를 누르면 첫 빌드가 시작된다.

3. basePath 제거

Cloudflare Pages는 루트에서 서빙되므로 basePath가 필요 없다. 제거한다.

next.config.mjs
const nextConfig = {
  output: 'export',
  // basePath: '/tera-log', — 삭제
  images: { unoptimized: true },
}

프로젝트 전체에서 basePath를 참조하던 부분도 정리해야 한다.

src/lib/constants.ts
export const BASE_PATH = '' // '/tera-log' → 빈 문자열로
 
export const SITE_CONFIG = {
  // ...
  url: 'https://tera-log.pages.dev', // GitHub Pages URL → Cloudflare URL
} as const

BASE_PATH를 빈 문자열로 바꾸면 기존에 ${BASE_PATH}/images/...처럼 사용하던 경로들이 자연스럽게 /images/...로 바뀐다. 참조하는 곳을 하나하나 고칠 필요가 없다.

4. 배포 확인

GitHub에 푸시하면 Cloudflare가 자동으로 빌드하고 배포한다. 빌드 로그에서 다음을 확인한다:

✓ Compiled successfully
✓ Generating static pages (12/12)
✓ Exporting (2/2)

배포가 완료되면 https://tera-log.pages.dev에서 블로그를 확인할 수 있다.

SEO 설정

배포만 하면 구글에 자동으로 노출되지 않는다. 검색엔진이 사이트를 크롤링할 수 있도록 몇 가지 설정이 필요하다.

robots.txt

검색엔진 크롤러에게 "이 사이트를 크롤링해도 된다"고 알려주는 파일이다.

public/robots.txt
User-agent: *
Allow: /
 
Sitemap: https://tera-log.pages.dev/sitemap.xml

public/ 디렉토리에 넣으면 빌드 시 out/ 디렉토리에 자동으로 복사된다.

sitemap.xml 자동 생성

sitemap은 사이트의 모든 페이지 목록을 검색엔진에 알려주는 파일이다. 글을 추가할 때마다 수동으로 수정하면 번거로우니, 빌드 시 자동 생성되도록 스크립트를 만들었다.

scripts/generate-sitemap.mjs
import fs from 'node:fs'
import path from 'node:path'
import matter from 'gray-matter'
 
const SITE_URL = 'https://tera-log.pages.dev'
const CONTENT_DIR = path.join(process.cwd(), 'content')
const OUTPUT_PATH = path.join(process.cwd(), 'public', 'sitemap.xml')
 
function getPostEntries() {
  return fs
    .readdirSync(CONTENT_DIR)
    .filter((file) => file.endsWith('.mdx'))
    .map((file) => {
      const content = fs.readFileSync(
        path.join(CONTENT_DIR, file), 'utf-8'
      )
      const { data } = matter(content)
      const slug = file.replace('.mdx', '')
 
      return {
        url: `${SITE_URL}/posts/${slug}`,
        lastmod: new Date(data.date).toISOString().split('T')[0],
      }
    })
}
 
function buildSitemap(entries) {
  const today = new Date().toISOString().split('T')[0]
 
  const urls = [
    `  <url>
    <loc>${SITE_URL}</loc>
    <lastmod>${today}</lastmod>
    <changefreq>weekly</changefreq>
    <priority>1.0</priority>
  </url>`,
    ...entries.map(
      (entry) => `  <url>
    <loc>${entry.url}</loc>
    <lastmod>${entry.lastmod}</lastmod>
    <changefreq>monthly</changefreq>
    <priority>0.7</priority>
  </url>`
    ),
  ]
 
  return `<?xml version="1.0" encoding="UTF-8"?>
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
${urls.join('\n')}
</urlset>`
}
 
const entries = getPostEntries()
const sitemap = buildSitemap(entries)
fs.writeFileSync(OUTPUT_PATH, sitemap, 'utf-8')

package.jsonprebuild 스크립트를 추가하면, pnpm build 실행 시 sitemap이 먼저 생성된다.

package.json
{
  "scripts": {
    "prebuild": "node scripts/generate-sitemap.mjs",
    "build": "next build"
  }
}

Google Search Console 등록

  1. Google Search Console에서 URL 접두어 방식으로 https://tera-log.pages.dev 등록
  2. 인증 방식은 HTML 파일 업로드 선택 — 다운로드한 파일을 public/에 넣으면 된다
  3. 인증 완료 후 Sitemaps 메뉴에서 sitemap.xml 제출

*.pages.dev 도메인은 DNS 레코드를 수정할 수 없기 때문에, DNS 인증 방식은 사용할 수 없다. HTML 파일 또는 메타 태그 방식을 사용해야 한다.

등록 후 구글이 크롤링하고 인덱싱하는 데 며칠에서 몇 주 걸릴 수 있다.

GitHub Actions 정리

Cloudflare Pages로 완전히 이전했다면, 기존 GitHub Pages 배포 워크플로우는 더 이상 필요 없다.

# 사용하지 않는 워크플로우 삭제
rm .github/workflows/deploy.yml

GitHub 레포 > Settings > Pages에서도 GitHub Pages를 비활성화해두면 깔끔하다.

트러블슈팅

CSS가 깨진다

basePath를 제거하지 않으면 발생한다. Cloudflare Pages는 루트(/)에서 서빙하는데, CSS/JS 파일을 /tera-log/_next/...에서 찾으려고 하기 때문이다. next.config.mjs에서 basePath를 제거하고, BASE_PATH를 빈 문자열로 바꿔야 한다.

빌드는 성공하는데 배포가 실패한다

Build output directory가 out으로 설정되어 있는지 확인한다. Next.js의 output: 'export'는 기본적으로 out/ 디렉토리에 빌드 결과물을 생성한다.

페이지 새로고침 시 404

Next.js 정적 빌드는 각 경로마다 HTML 파일을 생성한다. /posts/my-post라면 out/posts/my-post.html이 존재해야 한다. generateStaticParams에서 모든 경로를 반환하고 있는지 확인한다.

최종 비교

GitHub PagesCloudflare Pages
URLterajh.github.io/tera-logtera-log.pages.dev
basePath 필요OX
CDNX글로벌
대역폭100GB/월무제한
설정 난이도Actions 직접 작성Git 연동만
비용무료무료

같은 무료인데 제한이 적고, 설정도 간단하고, CDN까지 붙는다. 정적 블로그라면 Cloudflare Pages가 더 나은 선택이다.

관심 있을 만한 포스트

Next.js 블로그 만들기 — 검색엔진 등록 4종 완전 정복

Google Search Console, Bing Webmaster Tools, 네이버 서치어드바이저, 다음 검색등록까지. Next.js 블로그를 검색엔진에 노출시키는 전체 과정을 정리했다.

SEO검색엔진

Next.js 블로그 만들기 — 정적 블로그에 맞춤 추천 포스트 기능 추가

localStorage에 조회 이력을 저장하고, 태그 가중치 스코어링으로 정적 블로그에서도 개인화 추천을 구현하는 방법.

Next.jslocalStorage

Bun vs Node.js vs Deno — 뭐가 다른지, 그래서 뭘 쓰면 좋은지 (2026 기준)

런타임 3대장 비교: 호환성(Node), 속도/번들(Bun), 올인원/보안(Deno). 팀/프로덕트 상황별 선택 기준과 체크리스트까지 정리.

BunNode.js

번들러(Bundle)란 뭐고, 왜 필요할까? — 요즘 번들러/빌드 툴 비교 가이드

번들러의 역할(모듈/의존성/트랜스파일/최적화)을 쉽게 설명하고, Vite·Rollup·esbuild·Webpack·Rspack·Turbopack 같은 도구를 상황별로 비교합니다.

BundlerVite

Claude Code 에이전트 팀 — 여러 AI가 협업하는 새로운 방식

Claude Code의 실험적 기능인 에이전트 팀을 정리했다. 여러 Claude 인스턴스가 리더-팀원 구조로 작업을 분담하고, 서로 메시지를 주고받으며 병렬로 협업하는 구조다.

Claude CodeAI

Next.js 블로그 만들기 — 정적 블로그에 검색 기능 추가

빌드 타임 검색 인덱스 생성과 클라이언트 사이드 필터링으로 정적 블로그에 검색 기능을 구현하기. Cmd+K 단축키, 오버레이 UI까지.

검색UX

Next.js 블로그 만들기 — 스크롤 프로그레스 바와 Canvas 렌더링 이슈 해결

스크롤 진행률 프로그레스 바 구현과 Canvas 커서 효과가 GNB backdrop-blur와 충돌하며 발생한 깜빡임 이슈 해결기.

CanvasUX

Next.js 블로그 만들기 — TOC와 커서 효과로 디테일 살리기

IntersectionObserver 기반 TOC(Table of Contents)와 Canvas 커서 트레일 효과 구현기. 스크롤 하이라이팅, fixed 레이아웃 처리까지.

TOCCanvas