정적 자산으로 도움말 센터 제공

CloudflareBeginner
지금 연습하기

소개

도움말 센터에는 빠르게 제공되는 공개 페이지, JSON 상태 확인 엔드포인트, 직원 전용 문서가 필요합니다. 정적 파일이 요청을 처리하도록 작성된 코드보다 우선 처리되는 경우가 있습니다. 이 실습에서는 먼저 로컬에서 이러한 동작을 확인한 다음, Worker 우선 라우팅을 구성하고, 공개 사이트는 계속 사용할 수 있으면서 합성 직원용 테스트 파일은 보호하는 정책을 배포합니다.

이 독립 실습은 Node.js 22.22.0, 프로젝트 로컬 Wrangler 4.131.1, 제공된 HTML/CSS/JavaScript 테스트 파일이 준비된 /home/labex/project/help-center에서 시작합니다. 직접 만든 Cloudflare 학습 계정과 앞서 배운 인증, 배포 및 비밀 파일 관리 기술을 사용합니다. 이전 VM, 클라우드 리소스, 구매한 도메인, 데이터베이스 또는 유료 업그레이드는 필요하지 않습니다. 요청은 일반 계정 사용량에 포함됩니다.

모든 콘텐츠와 자격 증명은 합성 데이터입니다. 터미널 하나를 계속 열어 두세요. 안전하지 않은 초기 구성은 로컬에만 유지하고, 수정된 Worker 만 배포합니다. VM 을 종료하기 전에 배포를 삭제하고 로컬 테스트 자격 증명을 제거한 뒤 로그아웃합니다.

로컬에서 자산 우선 라우팅 확인

이 단계에서는 제공된 도움말 센터 기본 구성을 살펴보고, 기본 설정에서 일치하는 파일이 Worker 보다 우선 처리되는 것을 확인합니다. 테스트 파일에는 의도적으로 충돌을 일으키는 /api/health 파일과 가짜 직원 핸드북이 포함되어 있습니다. 모든 데이터는 합성 데이터이며, 첫 번째 구성은 로컬에서만 사용합니다.

cd /home/labex/project/help-center
node --version
npx wrangler --version
ls -R public

Node 는 v22.22.0, Wrangler 는 4.131.1 로 표시되어야 합니다. 설정 과정에서 프로젝트 로컬 도구가 설치되었습니다. 다른 환경에서 재현할 때는 프로젝트 lockfile 과 함께 npm ci를 사용합니다. public 디렉터리에는 HTML, CSS, 브라우저용 JavaScript 와 두 개의 라우팅 테스트 파일이 있습니다. 이 디렉터리에 자격 증명이나 실제 내부 문서를 넣지 마세요.

WORKER_NAME="labex-help-$(openssl rand -hex 6)"
cat > wrangler.jsonc <<CONFIG
{
  "name": "$WORKER_NAME",
  "main": "src/index.js",
  "compatibility_date": "2026-09-14",
  "workers_dev": true,
  "preview_urls": false,
  "assets": {
    "directory": "./public",
    "binding": "ASSETS",
    "html_handling": "none",
    "not_found_handling": "none",
    "run_worker_first": false
  }
}
CONFIG

directory는 업로드할 파일을 지정하고, binding은 핸들러에서 env.ASSETS로 파일에 접근할 수 있게 합니다. html_handling: none은 명시적인 파일 경로를 그대로 사용합니다. not_found_handling: none은 자동 SPA 대체 페이지를 사용하지 않게 합니다. 자동 HTML 처리를 비활성화했으므로 핸들러는 //index.html에 명시적으로 매핑합니다. 핸들러는 상태 확인 요청에는 JSON 을 반환하고, 그 외의 경로는 자산 저장소로 전달합니다.

cat > src/index.js <<'JS'
export default {
  async fetch(request, env) {
    if (new URL(request.url).pathname === '/api/health') {
      return Response.json({status: 'ok', service: 'help-center'});
    }
    const assetUrl = new URL(request.url);
    if (assetUrl.pathname === '/') assetUrl.pathname = '/index.html';
    return env.ASSETS.fetch(new Request(assetUrl, request));
  }
};
JS
npx wrangler dev --ip 0.0.0.0 --port 8080 > dev.log 2>&1 &
cat dev.log

계속하기 전에 로그에 준비 완료 상태가 표시될 때까지 기다립니다. 필요하면 cat 명령을 다시 실행합니다.

curl -i http://127.0.0.1:8080/
curl -i http://127.0.0.1:8080/styles.css
curl -i http://127.0.0.1:8080/api/health
curl -i http://127.0.0.1:8080/staff/handbook.html

홈 페이지와 CSS 는 200 을 반환합니다. 상태 확인 요청은 핸들러의 JSON 이 아니라 정적 텍스트 STATIC_HEALTH_PLACEHOLDER를 반환합니다. 일치하는 자산이 우선 처리되기 때문입니다. 합성 핸드북도 인증 없이 직접 읽을 수 있습니다. 이는 라우팅 우선순위를 보여 주는 예이며, 안전한 배포 구성이 아닙니다. 이 초기 구성을 배포하지 마세요. 변경하기 전에 검증 결과를 확인합니다.

실습 환경에 Web 8080 미리보기가 표시된다면 지금 엽니다. 도움말 센터 기본 화면은 표시되지만 상태 줄에는 브라우저가 JSON 을 받지 못했기 때문에 API 상태를 사용할 수 없다고 표시됩니다. CLI 응답을 라우팅 확인의 기준으로 사용하고, 미리보기는 화면 상태를 확인하는 용도로만 사용합니다.

아래 예시는 시작 상태의 문제를 보여 줍니다. 페이지와 스타일시트는 로드되지만, API status unavailable은 브라우저가 예상한 상태 확인 JSON 을 받지 못했다는 뜻입니다. 페이지 기본 화면이 표시되는 것만으로는 API 라우팅이 정상이라고 판단할 수 없습니다.

라우팅 수정 전 도움말 센터. API status unavailable 이 표시됩니다

자산보다 먼저 Worker 를 실행하고 직원 콘텐츠 보호

이 단계에서는 정적 자산과 일치하는지 확인하기 전에 핸들러를 먼저 실행하도록 구성합니다. jobs에 표시된 실제 개발 작업을 중지합니다. 아래 예시는 작업 번호가 1 이라고 가정합니다.

jobs
kill %1
cat > wrangler.jsonc <<CONFIG
{
  "name": "$WORKER_NAME",
  "main": "src/index.js",
  "compatibility_date": "2026-09-14",
  "workers_dev": true,
  "preview_urls": false,
  "assets": {
    "directory": "./public",
    "binding": "ASSETS",
    "html_handling": "none",
    "not_found_handling": "none",
    "run_worker_first": true
  }
}
CONFIG

run_worker_first: true로 설정하면 원래 정적 파일과 직접 일치하는 요청을 포함해 모든 요청이 핸들러로 들어갑니다. 특정 경로 패턴만 선택하는 방식도 있지만, 이 작은 애플리케이션에서는 하나의 명시적인 라우팅 정책을 사용합니다. 자세한 내용은 Static Assets configuration을 참고하세요.

앞서 배운 비밀 파일 절차를 사용해 일회용 직원 자격 증명을 생성합니다. umask는 새로 만드는 파일의 권한을 제한합니다. 이 값은 실습 전용 bearer 토큰이며 계정 API 토큰이 아닙니다. 이 값을 공개 파일, 브라우저용 JavaScript, URL 또는 로그에 포함하지 마세요.

umask 077
STAFF_TOKEN=$(openssl rand -hex 24)
printf 'STAFF_TOKEN=%s\n' "$STAFF_TOKEN" > .dev.vars
cat .gitignore

.dev.vars*.env*가 무시되는지 확인합니다. 아래의 완전한 정책으로 핸들러를 교체합니다. 이 코드는 경로를 한 번만 디코딩하고, 상태 확인 요청에는 JSON 을 반환하며, 지정된 공개 파일만 허용합니다. 직원 자격 증명을 확인한 후에만 해당 자산을 가져오고, 알 수 없는 경로는 거부합니다. ASSETS 로 전송되는 요청에는 클라이언트의 Authorization 헤더가 포함되지 않습니다. 보호된 응답에는 private, no-store를 사용합니다.

cat > src/index.js <<'JS'
export default {
  async fetch(request, env) {
    const url = new URL(request.url);
    let path;
    try { path = decodeURIComponent(url.pathname); }
    catch { return Response.json({error: 'not_found'}, {status: 404}); }
    if (path === '/api/health') {
      if (request.method !== 'GET') {
        return Response.json({error: 'method_not_allowed'}, {status: 405, headers: {Allow: 'GET'}});
      }
      return Response.json({status: 'ok', service: 'help-center'});
    }
    const publicPaths = ['/', '/index.html', '/styles.css', '/app.js'];
    if (path === '/staff/handbook.html') {
      if (!env.STAFF_TOKEN) return Response.json({error: 'staff_unconfigured'}, {status: 503});
      if (request.headers.get('Authorization') !== `Bearer ${env.STAFF_TOKEN}`) {
        return Response.json({error: 'unauthorized'}, {status: 401});
      }
    } else if (!publicPaths.includes(path)) {
      return Response.json({error: 'not_found'}, {status: 404});
    }
    if (!['GET', 'HEAD'].includes(request.method)) {
      return Response.json({error: 'method_not_allowed'}, {status: 405, headers: {Allow: 'GET, HEAD'}});
    }
    url.pathname = path === '/' ? '/index.html' : path;
    // Only known paths reach the asset store, after any required authorization.
    const response = await env.ASSETS.fetch(new Request(url, {method: request.method}));
    if (path === '/staff/handbook.html') {
      const headers = new Headers(response.headers);
      headers.set('Cache-Control', 'private, no-store');
      return new Response(response.body, {status: response.status, headers});
    }
    return response;
  }
};
JS
npx wrangler dev --ip 0.0.0.0 --port 8080 > dev.log 2>&1 &
cat dev.log

준비 완료 상태가 표시되면 공개 경로, 보호된 경로, 알 수 없는 경로의 응답을 비교합니다.

curl -i http://127.0.0.1:8080/api/health
curl -i http://127.0.0.1:8080/staff/handbook.html
curl -i http://127.0.0.1:8080/staff/handbook.html -H "Authorization: Bearer wrong-token"
curl -i http://127.0.0.1:8080/staff/handbook.html -H "Authorization: Bearer $STAFF_TOKEN"
curl -i --path-as-is http://127.0.0.1:8080/%73taff/handbook.html
curl -i http://127.0.0.1:8080/missing-page -H "Sec-Fetch-Mode: navigate"

이제 상태 확인 요청은 충돌하는 파일이 여전히 존재해도 200 과 함께 {"status":"ok","service":"help-center"}를 반환합니다. 자격 증명이 없거나 잘못된 경우에는 401 JSON 을 반환하고, 일치하는 토큰을 사용하면 합성 핸드북 HTML 을 반환합니다. 인코딩된 직원 경로도 401 을 반환하며, 알 수 없는 탐색 요청은 404 JSON 을 반환합니다. 테스트 파일을 삭제하면 라우팅 문제를 숨길 수 있으므로 그대로 유지합니다.

선택 사항인 Web 8080 미리보기를 새로 고칩니다. 이제 상태 줄에 API status: ok 가 표시되어야 합니다. 자격 증명 없이 핸드북 엔드포인트에 접근하면 401 JSON 을 반환합니다. 일부 내장 브라우저는 이 응답으로 이동하는 것을 차단하고 이전 페이지를 계속 표시할 수 있습니다. 이 경우 위의 curl 결과로 응답을 확인합니다. 브라우저에 이전 페이지가 표시되는 것은 접근에 성공했다는 증거가 아닙니다. 인증된 접근은 합성 헤더를 포함한 curl 로 확인하고, 주소 표시줄에 비밀 값을 붙여 넣지 마세요. 서버가 실행 중인 상태에서 검증을 수행합니다. 검증 과정에서는 HEAD 요청, 인코딩된 경로, 다른 표기 방식, 공개 자산 유형도 확인합니다.

이전 미리보기와 상태 줄을 비교합니다. 이제 API status: ok가 표시되면 페이지가 상태 확인 응답을 읽을 수 있다는 뜻입니다. 이 화면 확인은 공개 상태 확인 경로를 검증합니다. 보호된 핸드북은 위의 curl 응답으로 확인합니다.

라우팅 수정 후 도움말 센터. API status ok 가 표시됩니다

자산과 보호된 핸들러 배포

이 단계에서는 수정된 구성만 학습 계정에 배포합니다. 현재 로컬 작업을 중지하되, jobs에서 실제 작업 번호를 확인해 사용합니다.

jobs
kill %1
npx wrangler login --device --browser=false --scopes account:read user:read workers_scripts:write workers_tail:read

로그인된 브라우저에서 표시된 장치 링크와 코드를 완료하고, 기존 Wrangler 권한과 Background Access 를 검토한 뒤 학습 계정을 선택합니다. 터미널 작업이 완료될 때까지 기다립니다.

npx wrangler whoami --json

실제 계정 이름과 ID 를 확인한 다음, 아래의 YOUR_ACCOUNT_ID를 해당 ID 로 바꿉니다. 기존 리소스 이름과 수정된 자산 설정은 그대로 유지합니다.

cat > wrangler.jsonc <<CONFIG
{
  "name": "$WORKER_NAME",
  "main": "src/index.js",
  "compatibility_date": "2026-09-14",
  "account_id": "YOUR_ACCOUNT_ID",
  "workers_dev": true,
  "preview_urls": false,
  "assets": {
    "directory": "./public",
    "binding": "ASSETS",
    "html_handling": "none",
    "not_found_handling": "none",
    "run_worker_first": true
  }
}
CONFIG
npx wrangler deploy

Wrangler 는 public 디렉터리를 업로드하고 핸들러를 배포합니다. 아래에 표시되는 정확한 workers.dev URL 을 복사합니다. 학습 계정의 기존 서브도메인을 다시 사용합니다. 처음 사용하는 경우 Wrangler 에 표시되는 사용 가능한 서브도메인 안내를 따릅니다.

APP_URL="https://YOUR_WORKER.YOUR_SUBDOMAIN.workers.dev"
curl -i "$APP_URL/staff/handbook.html"

비밀 값을 업로드하기 전에는 이 경로가 503 staff_unconfigured를 반환해야 합니다. 핸들러가 먼저 실행되고 접근을 차단하기 때문입니다. .dev.vars는 로컬 구성 파일이므로 배포되지 않습니다. 처음 응답이 호스트 이름 전파 지연으로 늦어지는 경우에는 지속적인 오류인지 조사하기 전에 잠시 기다렸다가 다시 시도합니다.

npx wrangler secret bulk .dev.vars
npx wrangler secret list

값을 표시하지 않고 STAFF_TOKENsecret_text로 나열되는지 확인합니다. 비밀 값 배포가 모든 서비스 위치에 반영되기까지 잠시 걸릴 수 있습니다. 다음 요청에서도 staff_unconfigured가 반환되면 10 초 기다린 뒤 최대 2 분 동안 해당 요청을 반복합니다. 검증을 진행하기 전에 자격 증명 없이 안정적으로 401 을 반환하고, 자격 증명을 사용하면 200 을 반환하는지 확인합니다. 불일치가 계속되면 원인을 조사해야 합니다. 최종 결과로 503 을 받아들이거나, 검사를 통과시키기 위해 인증 정책을 변경하지 마세요.

curl -i "$APP_URL/"
curl -i "$APP_URL/api/health"
curl -i "$APP_URL/staff/handbook.html"
curl -i "$APP_URL/staff/handbook.html" -H "Authorization: Bearer $STAFF_TOKEN"
curl -i "$APP_URL/missing-page" -H "Sec-Fetch-Mode: navigate"

공개 HTML, 상태 확인 JSON, 자격 증명 없이 401, 자격 증명과 함께 핸드북 HTML, 알 수 없는 페이지에 대한 404 가 반환되어야 합니다. 같은 Dashboard 계정에서 Compute → Workers & Pages를 열고 해당 Worker 를 찾아 공개 URL 을 확인합니다. 실제 소유권, 배포된 바인딩, 자산 내용 및 인증 동작은 검증 명령으로 확인합니다. 자신의 브라우저에서 공개 홈 페이지를 열어도 되지만, 직원 토큰을 URL 로 전송하지 마세요. 이 합성 토큰 검사는 라우팅 학습을 위한 것이며, 완전한 직원 신원 관리 시스템이 아닙니다.

Worker 의 Overview 탭에서 이동 경로에 표시된 이름과 배포 출력에 표시된 workers.dev 주소가 일치하는지 비교합니다. 이 스크린샷의 이름과 서브도메인은 예시이며, 실제로 생성된 이름과 계정 서브도메인은 다릅니다. 이 주소는 배포된 공개 주소이고, Web 8080 은 로컬 개발 서버를 미리 보여 줍니다. 이미 존재하는 이 Worker 를 여는 데 새 애플리케이션을 만들 필요는 없습니다.

Overview 에 표시된 배포된 도움말 센터 Worker 와 공개 주소

도움말 센터 배포 삭제

이 단계에서는 인증이 유지된 상태에서 실습용 Worker 와 연결된 자산 및 비밀 바인딩을 삭제합니다. 고유한 이름과 계정을 먼저 확인합니다.

cat wrangler.jsonc
npx wrangler delete

프롬프트에 표시된 실습용 이름이 정확한지 확인한 다음 단일 키 y를 누릅니다. Wrangler 4.131.1 에서는 삭제 후 문서에 기록된 기존 Workers Sites KV 인증 오류가 표시될 수 있습니다. 권한을 확대하거나 이 메시지를 삭제 증거로 사용하지 마세요. Workers & Pages 를 새로 고치고 검증을 수행합니다. 인증된 리소스 목록에서 해당 이름이 사라졌는지 확인해야 합니다. 관련 없는 리소스, 계정 및 해당 workers.dev 서브도메인은 유지합니다.

로컬 자격 증명 제거 및 연결 해제

이 단계에서는 클라우드 정리를 확인한 후 로컬 합성 자격 증명을 제거하고 VM 연결을 종료합니다.

rm .dev.vars
unset STAFF_TOKEN
npx wrangler logout
npx wrangler whoami --json

명시적인 "loggedIn": false가 표시되어야 합니다. 이 구조화된 결과가 표시되었다면 인증되지 않은 상태로 인해 종료 상태가 0 이 아닌 것은 정상입니다. 검증을 수행한 뒤 VM 을 종료합니다. 브라우저 로그인 상태는 계속 유지될 수 있습니다. VM 을 종료하거나 로그아웃해도 클라우드 리소스가 자동으로 삭제되지는 않습니다.

요약

자산 우선 라우팅을 확인한 다음, Worker 우선 처리를 사용해 일치하는 파일보다 API 응답과 인증이 먼저 실행되도록 구성했습니다. 제공된 도움말 센터 기본 구성은 공개 HTML, CSS, 브라우저용 JavaScript 를 유지하면서 명시적인 경로 처리를 통해 인증되지 않은 직원 요청과 알 수 없는 경로를 차단했습니다. 인코딩된 경로와 브라우저 방식의 탐색 요청을 테스트하고, 수정된 사이트를 별도의 비밀 값 업로드와 함께 배포한 뒤 삭제와 로그아웃을 확인했습니다.

정적 파일과 애플리케이션 정책이 같은 호스트 이름을 공유하는 경우 라우팅 순서를 의도적으로 선택해야 합니다. 로컬 응답만으로는 배포된 구성이나 계정 소유자가 올바르다는 것을 증명할 수 없습니다.