소개
문서 사이트에서 페이지를 이동할 때는 기존 링크가 계속 올바른 위치로 연결되어야 합니다. 리디렉션은 브라우저에 다른 URL 을 요청하도록 알리는 HTTP 응답입니다. 이 실습에서는 KV 에 이전 경로와 새 문서 경로의 매핑을 저장하는 작은 카탈로그를 만듭니다.
가져오기 전에 제공된 JSON 데이터 세트를 검사하고 검증한 다음, 여러 페이지에 걸쳐 카탈로그를 읽습니다. **페이지 매김 (pagination)**은 제한된 결과 묶음을 요청하고, 계속 진행하기 위한 마커를 사용해 다음 묶음을 가져오는 방식입니다. 마지막으로 대상 경로 하나를 변경하고 항목 두 개를 정리하면서 같은 네임스페이스에 있는 관련 없는 레코드는 보존합니다. 이 방식은 한 번에 키 하나씩 수정하지 않고 설정 모음을 관리할 때 유용합니다.
먼저 이전 KV 가이드 실습을 완료합니다. 이 새 VM 에는 /home/labex/project/redirect-catalog에 Node.js 22.22.0 과 프로젝트 로컬 Wrangler 4.131.1 이 준비되어 있습니다. 설정 과정에서 테스트용 리디렉션 다섯 개를 제공하지만, 이를 가져오거나 클라우드 리소스를 만들지는 않습니다. account-read, Worker-write 및 KV-write 권한이 있는 학습 계정을 사용합니다. 이 작은 데이터 세트에는 임시 Worker 하나와 네임스페이스 하나면 충분하며, 유료 업그레이드나 구매한 도메인은 필요하지 않습니다. 공개 카탈로그에는 예시 경로만 포함됩니다.
리디렉션 네임스페이스 연결
이 단계에서는 작은 리디렉션 카탈로그에 사용할 독립적인 네임스페이스를 연결합니다. ROUTES 바인딩은 명령줄 작업과 Worker 에서 이 네임스페이스를 식별합니다. 각 실습은 자체 리소스로 시작하므로 이 카탈로그는 이전 네임스페이스에 영향을 주지 않습니다.
준비된 프로젝트로 이동합니다.
cd /home/labex/project/redirect-catalog
한 번만 사용할 고유한 이름을 생성합니다. openssl rand -hex 6은 무작위 접미사를 출력하고, $(...)는 이 값을 이름에 삽입합니다. 셸 변수에 이름을 저장하면 이 터미널에서 이어지는 명령에 사용할 수 있습니다.
WORKER_NAME="labex-routes-$(openssl rand -hex 6)"
printf '%s\n' "$WORKER_NAME"
이 VM 을 인증합니다. 계정 ID 를 읽는 권한 외에도 Workers Scripts Write 권한으로 배포 및 삭제를 수행할 수 있고, Workers KV Write 권한으로 이 실습의 네임스페이스와 키를 관리할 수 있습니다.
npx wrangler login --device --browser=false --scopes account:read user:read workers_scripts:write workers_kv:write
브라우저에서 표시된 디바이스 링크를 열고 현재 코드를 입력합니다. 요청된 권한과 학습 계정을 확인한 다음 Wrangler 를 승인합니다. 동의 페이지에 백그라운드 액세스 권한이 표시될 수도 있습니다. 터미널로 돌아와 로그인이 완료될 때까지 기다립니다.
Create a Feature Flag Store 에서 소개한 것과 동일한 Worker 및 KV 쓰기 권한을 확인합니다. 승인하기 전에 학습 계정을 확인합니다.
npx wrangler whoami --json
loggedIn: true와 학습 계정의 name을 확인합니다. 계정이 하나만 표시되어도 확인해야 합니다. 해당 계정의 id를 복사합니다. 아래 구성에서 YOUR_ACCOUNT_ID를 복사한 값으로 바꾼 후 명령을 실행합니다. 여기서 cat의 here-document 는 두 JSON 줄 사이의 모든 내용을 파일에 기록하고, >는 파일을 덮어씁니다. 따옴표 없는 구분자는 셸이 $WORKER_NAME을 삽입하도록 합니다.
cat > wrangler.jsonc <<JSON
{
"name": "$WORKER_NAME",
"main": "src/index.js",
"compatibility_date": "2026-07-30",
"account_id": "YOUR_ACCOUNT_ID",
"workers_dev": true
}
JSON
해당 계정에 네임스페이스를 생성합니다. 네임스페이스 제목에 Worker 의 고유한 이름을 포함하므로 나중에 두 리소스를 쉽게 구분할 수 있습니다. --update-config=false는 바인딩을 자동으로 파일에 추가하지 않고 직접 편집할 수 있도록 합니다.
npx wrangler kv namespace create "$WORKER_NAME-routes" --update-config=false
출력에 새 네임스페이스 ID 가 포함됩니다. 이 ID 를 복사한 다음, 아래 전체 구성에서 YOUR_ACCOUNT_ID와 YOUR_NAMESPACE_ID를 각각 실제 값으로 바꿉니다. ROUTES 바인딩 이름은 코드에서 사용할 이름이고, ID 는 실제 Cloudflare 리소스를 식별합니다.
cat > wrangler.jsonc <<JSON
{
"name": "$WORKER_NAME",
"main": "src/index.js",
"compatibility_date": "2026-07-30",
"account_id": "YOUR_ACCOUNT_ID",
"workers_dev": true,
"kv_namespaces": [
{ "binding": "ROUTES", "id": "YOUR_NAMESPACE_ID" }
]
}
JSON
npx wrangler kv namespace list
이 실습의 네임스페이스 제목을 찾아 ID 가 파일에 입력한 값과 같은지 비교합니다. 다른 네임스페이스가 표시될 수 있지만 그대로 둡니다. 이 구성은 이후 명령에서 사용할 계정과 리소스를 지정합니다. 바인딩은 네임스페이스를 가리키는 참조이며, 데이터의 복사본이 아닙니다.
작은 카탈로그 검증 및 가져오기
이 단계에서는 한 번의 명령으로 모든 항목을 쓰기 전에 데이터 세트를 확인합니다. 일괄 작업을 사용하면 반복 작업을 줄일 수 있지만, 제공된 데이터에 있는 오류도 전체 항목에 반복해서 적용됩니다. 먼저 준비된 파일을 읽습니다.
cat redirects.json
각 객체에는 route:/old-start와 같은 key, /docs/start와 같은 value가 있습니다. route: 접두사는 카탈로그 레코드를 그룹화하며 디렉터리가 아닙니다. 대상은 임의의 외부 URL 이 아니라 이 사이트의 경로입니다.
일반적인 Node.js 검증 스크립트를 작성합니다. 이 스크립트는 파일 이름을 읽고 배열과 필드를 확인하며, 중복 키를 거부하고 모든 항목이 검증을 통과한 후에만 개수를 출력합니다. Set은 이미 확인한 키를 기억합니다. 정규 표현식은 이 실습 데이터 세트를 단순한 이전 경로와 문서 대상 경로로 제한합니다. 이는 이 애플리케이션의 규칙이며 KV 가 강제하는 제한은 아닙니다.
cat > validate-redirects.mjs <<'JS'
import { readFile } from "node:fs/promises";
const filename = process.argv[2] ?? "redirects.json";
const entries = JSON.parse(await readFile(filename, "utf8"));
if (!Array.isArray(entries) || entries.length === 0 || entries.length > 20) {
throw new Error("Use a non-empty teaching dataset of at most 20 entries.");
}
const seen = new Set();
for (const entry of entries) {
if (!entry || typeof entry.key !== "string" || !/^route:\/old-[a-z-]+$/.test(entry.key)) {
throw new Error("Every key must name an old route, such as route:/old-start.");
}
if (typeof entry.value !== "string" || !/^\/docs\/[a-z-]+$/.test(entry.value)) {
throw new Error("Every destination must be a /docs/ path on this site.");
}
if (Object.keys(entry).some(key => !["key", "value"].includes(key))) {
throw new Error("This dataset accepts only key and value fields.");
}
if (seen.has(entry.key)) throw new Error(`Duplicate key: ${entry.key}`);
seen.add(entry.key);
}
console.log(`Validated ${entries.length} unique redirect entries.`);
JS
node validate-redirects.mjs redirects.json
Validated 5 unique redirect entries.가 출력되어야 합니다. 검증에 실패하면 가져오기 전에 파일을 수정합니다. 같은 키를 다시 쓰면 해당 값이 바뀌므로 중복 키를 거부하는 것이 중요합니다.
먼저 로컬에 route 가 아닌 테스트 항목을 만든 다음 카탈로그를 가져옵니다. 이 테스트 항목은 이후 카탈로그를 유지 관리할 때 다른 데이터가 보존되는지 확인하는 데 사용합니다.
npx wrangler kv key put system:owner labex-redirect-demo --binding ROUTES --local
npx wrangler kv bulk put redirects.json --binding ROUTES --local
npx wrangler kv key list --binding ROUTES --local
route: 항목 다섯 개와 system:owner가 표시되어야 합니다. bulk put은 파일에 있는 항목을 쓰지만 네임스페이스 전체를 바꾸거나 파일에 없는 키를 삭제하지는 않습니다. 또한 모든 위치에 동시에 표시되는 원자적 변경을 보장하지도 않습니다.
이제 동일하게 검토한 데이터 세트를 이 실습의 클라우드 네임스페이스로 가져옵니다.
npx wrangler kv key put system:owner labex-redirect-demo --binding ROUTES --remote
npx wrangler kv bulk put redirects.json --binding ROUTES --remote
npx wrangler kv key list --binding ROUTES --remote
키가 여섯 개인지 확인합니다. 명시적인 대상 플래그를 사용하면 로컬 작업과 클라우드 쓰기를 분리할 수 있습니다. 항목을 변경하기 전에 이 단계의 확인 작업을 실행합니다.
모든 페이지 읽기 및 리디렉션 제공
이 단계에서는 모든 route 키를 나열하고 리디렉션을 제공하는 Worker 를 작성합니다. KV 의 list() 호출 한 번으로 컬렉션의 일부만 반환될 수 있습니다. cursor는 KV 가 제공하는 계속 진행용 마커이므로 변경하지 않고 다시 전달해 다음 부분을 요청합니다.
다음 핸들러를 작성합니다. 의도적으로 limit: 2를 사용하면 레코드가 다섯 개뿐이어도 페이지 매김을 확인할 수 있습니다. 실제 운영 코드에서는 일반적으로 더 큰 페이지 크기를 사용하지만, 이 실습에서는 루프를 작게 유지하기 위해 데이터를 스무 개로 제한합니다.
cat > src/index.js <<'JS'
export default {
async fetch(request, env) {
const url = new URL(request.url);
try {
if (url.pathname === "/catalog") {
const names = [];
let cursor;
let complete = false;
let pages = 0;
do {
const page = await env.ROUTES.list({ prefix: "route:", limit: 2, cursor });
names.push(...page.keys.map(key => key.name));
pages += 1;
complete = page.list_complete;
cursor = complete ? undefined : page.cursor;
if ((!complete && !cursor) || pages > 20) {
return Response.json({ error: "Catalog could not be completed" }, { status: 503 });
}
} while (!complete);
return Response.json({ keys: names, pages });
}
if (url.pathname.startsWith("/docs/")) {
return new Response(`Example destination: ${url.pathname}`);
}
const target = await env.ROUTES.get(`route:${url.pathname}`);
if (target === null) return new Response("Not found", { status: 404 });
if (!/^\/docs\/[a-z-]+$/.test(target)) {
return Response.json({ error: "Invalid redirect destination" }, { status: 500 });
}
return Response.redirect(new URL(target, url.origin).href, 302);
} catch {
return Response.json({ error: "Redirect storage unavailable" }, { status: 503 });
}
}
};
JS
do...while 루프는 최소 한 페이지를 요청하고 list_complete가 true가 될 때까지 계속 실행합니다. 모든 요청에 prefix: "route:"를 유지하므로 owner 테스트 항목이 카탈로그에 포함되지 않습니다. names.push(...)는 각 페이지의 키 이름을 결과에 추가합니다.
keys 배열이 비어 있어도 목록 읽기가 끝났다는 뜻은 아닙니다. 삭제되었거나 만료된 항목 때문에 더 많은 페이지가 남아 있는 상태에서 키가 하나도 반환되지 않는 페이지가 생길 수 있습니다. 따라서 이 루프는 배열의 길이가 아니라 list_complete를 사용합니다. 페이지 수 제한과 cursor 누락 확인은 이 작은 데모에서 목록 읽기를 완료할 수 없을 때 통제된 오류를 반환합니다. 자세한 내용은 KV listing and pagination을 참조합니다.
다른 경로의 경우 Worker 는 일치하는 route 키를 읽습니다. 존재하지 않는 경로는 404를 반환하고, 지원되는 대상은 Location 헤더가 포함된 302 응답을 반환합니다. 런타임에서 대상 경로를 다시 확인하므로 KV 값이 잘못 편집되어도 방문자를 다른 사이트로 리디렉션할 수 없습니다. /docs/ 응답은 대상 경로를 보여 주는 간단한 자리 표시자이며, 완전한 문서 사이트가 아닙니다.
npx wrangler dev --local --ip 0.0.0.0 --port 8080 > local.log 2>&1 &
DEV_PID=$!
cat local.log
준비 완료 메시지가 표시될 때까지 기다린 다음 전체 카탈로그를 확인합니다.
curl -i http://127.0.0.1:8080/catalog
정렬된 route 키 다섯 개와 최소 세 개의 페이지가 표시되어야 합니다. 추가로 비어 있는 페이지가 표시될 수도 있습니다. 중요한 결과는 system:owner 항목 없이 전체 키 집합이 반환되는 것입니다.
curl -i http://127.0.0.1:8080/old-start
HTTP 302와 Location: http://127.0.0.1:8080/docs/start가 표시되어야 합니다. 기본적으로 curl 은 리디렉션을 따라가지 않고 리디렉션 응답만 표시합니다. 나중에 비교할 수 있도록 로컬 데이터 세트는 변경하지 않습니다.
선택한 경로 업데이트 및 다른 데이터 보존
이 단계에서는 네임스페이스 전체를 바꾸지 않고 클라우드 카탈로그를 수정합니다. 새 시작 페이지는 /docs/getting-started로 변경하고, 임시 페이지 두 개는 더 이상 리디렉션하지 않도록 합니다.
npx wrangler kv key put route:/old-start /docs/getting-started --binding ROUTES --remote
특정 키에 값을 쓰면 다른 경로는 그대로 유지됩니다. 여러 키를 삭제할 때 Wrangler 는 정확한 키 이름으로 구성된 JSON 배열을 사용합니다. 삭제하기 전에 이 작은 정리 목록을 읽습니다.
cat > retired-keys.json <<'JSON'
["route:/old-contact", "route:/old-event"]
JSON
cat retired-keys.json
npx wrangler kv bulk delete retired-keys.json --binding ROUTES --remote
확인 메시지가 표시되면 바인딩과 나열된 작업이 이 실습의 임시 네임스페이스를 가리키는지 확인합니다. 목록에는 route 키 두 개만 있으며 system:owner는 포함되지 않습니다.
npx wrangler kv key list --binding ROUTES --remote
npx wrangler kv key get system:owner --binding ROUTES --remote --text
남은 route 가 세 개이고 값 labex-redirect-demo가 변경되지 않았는지 확인합니다. 지금 원래의 일괄 가져오기를 다시 실행하지 마십시오. 이전 값으로 업데이트를 되돌리고 정리한 키를 다시 만들 수 있습니다.
Worker 를 배포하고 ROUTES 바인딩을 확인한 다음 실제 공개 주소를 복사합니다.
npx wrangler deploy
WORKER_URL="https://YOUR_WORKER.YOUR_SUBDOMAIN.workers.dev"
curl -i "$WORKER_URL/catalog"
먼저 /catalog가 HTTP 200과 예상 JSON 키 목록을 반환하는지 확인합니다. Cloudflare 오류 페이지가 나타나면 잠시 기다린 뒤 읽기 전용 요청을 반복하세요. 삭제된 경로는 HTTP 404와 애플리케이션 본문 Not found를 함께 반환해야 정상입니다. 상태 코드만으로 판단하지 마세요.
카탈로그에는 route:/old-pricing, route:/old-start, route:/old-support만 포함되어야 합니다. 변경된 경로와 정리한 경로를 테스트합니다.
curl -i "$WORKER_URL/old-start"
curl -i "$WORKER_URL/old-contact"
curl -i "$WORKER_URL/old-event"
시작 경로가 /docs/getting-started로 리디렉션되고, 정리한 두 경로는 모두 404를 반환해야 합니다. 새 클라우드 데이터가 아직 표시되지 않으면 KV 전파를 기다린 후 읽기 전용 확인을 다시 실행합니다. 연결 실패는 성공적으로 정리되었다는 결과가 아닙니다.
curl -i http://127.0.0.1:8080/catalog
로컬 개발 환경에는 여전히 원래 route 다섯 개가 표시됩니다. 이 차이는 유지 관리 명령이 클라우드 저장소를 대상으로 실행되었음을 확인해 줍니다. Dashboard 에서 같은 계정을 선택하고 Storage & databases → Workers KV를 연 다음 이 실습의 네임스페이스를 확인합니다. 명령 출력과 네임스페이스에 남은 route 세 개 및 owner 테스트 항목을 비교합니다. 이 확인 단계에서는 데이터를 읽기만 합니다. 생성된 네임스페이스 이름과 ID 는 이번 실행에 따라 달라집니다.
KV Pairs를 선택하여 아래 레코드를 확인합니다. 유지 관리 명령이 끝나기 전에 네임스페이스를 열었다면 Refresh로 새로 고치세요.

임시 클라우드 리소스 삭제
이 단계에서는 Wrangler 인증이 유지되는 동안 두 리소스를 모두 삭제합니다. 네임스페이스는 Worker 보다 오래 남을 수 있으므로 애플리케이션만 삭제해서는 데이터가 정리되지 않습니다.
이 터미널에서 시작한 로컬 개발 프로세스를 중지합니다.
kill "$DEV_PID"
삭제하기 전에 저장된 리소스 참조를 확인합니다.
cat wrangler.jsonc
labex-routes-... Worker 이름과 ROUTES 네임스페이스 ID 를 확인합니다. 이 구성으로 선택된 Worker 를 삭제합니다.
npx wrangler delete
확인 메시지가 표시되면 이름이 이 실습의 Worker 와 일치하는지 확인하고 y를 입력합니다. 그런 다음 ROUTES가 참조하는 네임스페이스만 삭제합니다.
npx wrangler kv namespace delete --binding ROUTES
확인 메시지가 표시되면 삭제 대상 네임스페이스를 검토한 후 승인합니다. 독립 확인 과정에서 삭제되어야 할 리소스를 식별할 수 있도록 wrangler.jsonc는 그대로 둡니다.
npx wrangler kv namespace list
이 실습의 네임스페이스가 없어야 하며, 관련 없는 네임스페이스는 남아 있어야 합니다. Dashboard 목록을 새로 고쳐 이 실습의 Worker 와 네임스페이스가 사라졌는지 확인합니다. 요청 실패나 로그인 만료만으로는 삭제가 증명되지 않습니다. 인증된 인벤토리를 확인할 수 있도록 로그아웃하기 전에 이 단계의 확인 작업을 실행합니다.
VM 인증 종료
이 단계에서는 정리 확인이 통과한 후 Wrangler 연결을 해제합니다. 로그아웃하면 이 VM 에 저장된 Wrangler 인증이 종료되지만, 클라우드 리소스가 삭제되거나 일반 Dashboard 브라우저 세션에서 로그아웃되는 것은 아닙니다.
npx wrangler logout
npx wrangler whoami --json
구조화된 결과에 "loggedIn": false가 표시되는지 확인합니다. 인증되지 않은 이 명령은 0 이 아닌 종료 상태로 끝날 수 있으며, 여기서는 정상입니다. 연결 오류만 표시되고 인증 상태가 명시되지 않으면 연결이 정상화된 후 다시 실행합니다.
남아 있는 로컬 파일과 로컬 KV 상태는 이 임시 VM 에 속합니다. 이미 삭제한 클라우드 리소스와는 별개입니다. 이제 실습을 마칠 수 있습니다.
요약
일괄 쓰기 전에 작은 리디렉션 데이터 세트를 검증하고, 로컬 대상과 클라우드 대상을 명시적으로 구분했으며, 접두사가 적용된 KV 목록의 모든 페이지를 순회했습니다. 경로 하나를 변경하고 정확한 키 두 개를 정리하면서 관련 없는 owner 레코드는 보존했습니다. 배포된 응답을 통해 새 대상 경로와 정리한 경로의 부재를 확인했으며, 로컬 카탈로그에는 원래 데이터가 그대로 남아 있었습니다.
마지막으로 임시 Worker 와 네임스페이스를 삭제하고 로그아웃했습니다. 다음에는 일시적으로 이전 버전을 반환할 수 있는 구성 읽기 작업을 처리합니다.



