블로그 개발 기록: CLAUDE.md로 토큰 아끼기, Vitest 검수 규칙, 메인 목록 페이지 나누기

· 약 7분 읽기 · 조회 2 #개발기록#Claude Code#SvelteKit#Vitest#CLAUDE.md

오늘은 Claude Code와 블로그를 손보면서 세 가지를 했다.

  1. CLAUDE.md 작성: 이 폴더에서 물어볼 때마다 코드를 다시 읽느라 토큰을 쓰지 않게 한다.
  2. 검수(checker) 규칙: 테스트 프레임워크를 붙이고, 검사가 통과해야 작업이 끝난 것으로 본다.
  3. 메인 목록 페이지 나누기: PC는 페이지 번호, 모바일은 더보기로 만든다.

1. CLAUDE.md: 매번 같은 걸 다시 읽지 않게

Claude Code는 프로젝트 루트의 CLAUDE.md를 세션이 시작될 때마다 읽는다. 여기에 프로젝트 구조와 규칙을 적어 두면 질문할 때마다 파일을 뒤져 가며 파악하는 과정이 줄어든다. /init으로 코드를 분석해 만들고, 필요한 것만 남겼다.

넣은 내용은 이렇다.

  • 명령어: dev, check, build, db:push. 배포는 git push가 아니라 vercel deploy --prod다.
  • 읽지 말 것: static/reports/(46MB), 자동 생성되는 reports.json, local.db, .svelte-kit/. 실수로 이걸 열면 토큰이 크게 나간다.
  • 여러 파일을 읽어야 알 수 있는 구조:
    • DB는 로컬 SQLite와 Turso를 환경변수로 자동 전환한다. 컬럼을 추가하면 schema.ts와 db.ts의 로컬 DDL 두 곳을 같이 고쳐야 한다.
    • 관리자 확인은 라우트 그룹이 아니라 기능마다 if (!locals.isAdmin)로 직접 한다.
    • 공개 페이지는 CDN에 1시간 캐시되고, POST 요청이 성공하면 캐시를 지운다.
    • 댓글·방명록은 {@html}로 출력하지 않는다.
  • 블로그 글 규칙: 제목, 본문, 슬러그 모두 한국어로 쓴다.

모든 파일을 나열하는 대신, 파일 하나만 봐서는 모르는 규칙을 적는 게 핵심이다. 파일 목록은 Claude가 직접 찾을 수 있다.

2. 검수 규칙: npm run verify가 통과해야 끝

이 블로그에는 테스트가 하나도 없었다. 검증 수단은 svelte-check 타입 검사뿐이었다. 코드를 고칠 때마다 실패를 줄이려고 테스트 프레임워크를 붙였다.

SvelteKit은 Vite 기반이라 Vitest가 가장 자연스럽다. 설정 파일을 따로 만들 필요 없이 vite.config.ts에 한 줄이면 된다.

// vite.config.ts
export default defineConfig({
	plugins: [sveltekit()],
	test: {
		include: ['src/**/*.test.ts']
	}
});

SvelteKit 플러그인이 그대로 붙어 있어서 $lib나 $env/dynamic/private를 import하는 코드도 테스트에서 그대로 돌아간다.

그리고 타입 검사, 테스트, 빌드를 한 번에 돌리는 명령을 만들었다.

"test": "vitest run",
"verify": "npm run check && npm run test && npm run build"

&&로 이었기 때문에 하나라도 실패하면 거기서 멈춘다.

첫 테스트는 DB 없이 돌아가는 순수 함수부터 골랐다. 깨지면 바로 사고가 나는 곳이다.

  • 예약 발행: 미래 시각으로 예약한 글이 미리 보이지 않는지, 한국 시간(KST) 변환이 맞는지
  • 마크다운 살균: <script>, onerror, javascript: 링크가 걸러지는지
  • 슬러그: 한글 제목이 sveltekit-블로그-개발-기록처럼 바뀌는지

마지막으로 CLAUDE.md에 규칙을 적었다.

  • npm run verify가 0으로 끝나야 작업 완료다.
  • 실패하면 원인을 고친다. 테스트를 지워서 통과시키지 않는다.
  • src/lib의 로직을 바꾸면 옆에 *.test.ts를 추가한다.
  • 보고할 때는 Tests 14 passed, 0 ERRORS 같은 실제 결과 줄을 인용한다.

마지막 규칙이 생각보다 중요하다. AI가 "통과했습니다"라고만 말하면 정말 돌렸는지 알 수 없다. 결과 줄을 보여 달라고 하면 돌리지 않고는 답할 수 없다.

3. 메인 목록: PC는 페이지 번호, 모바일은 더보기

메인 화면이 글을 최대 50개까지 한 번에 보여 줘서 너무 길었다. 한 페이지에 10개씩 나눴다.

주소로 상태를 들고 다닌다

PC와 모바일을 따로 만들지 않고 같은 주소 규칙 하나로 처리했다.

주소 보여 주는 글
/ 1~10번
/?page=2 11~20번 (PC 번호)
/?from=1&page=2 1~20번 (모바일 더보기)

from부터 page까지를 이어서 보여 주는 방식이다. 모바일 더보기는 /?from=1&page=3 같은 링크일 뿐이다. 그래서 새로고침하거나 뒤로 가도 보던 목록이 남고, 자바스크립트가 없어도 동작한다. SvelteKit의 data-sveltekit-noscroll을 링크에 붙이면 페이지를 넘겨도 스크롤 위치가 그대로라서 아래에 이어 붙는 것처럼 보인다.

주소 값을 DB 조회 범위로 바꾸는 부분은 작은 함수로 뺐다.

export function pageWindow(params: URLSearchParams, total: number, size = POSTS_PER_PAGE): PageWindow {
	const totalPages = Math.max(1, Math.ceil(total / size));
	const page = Math.min(positiveInt(params.get('page')) ?? 1, totalPages);
	const from = Math.min(positiveInt(params.get('from')) ?? page, page);
	return { page, from, totalPages, offset: (from - 1) * size, limit: (page - from + 1) * size };
}

?page=abc나 ?page=99 같은 잘못된 값은 가장 가까운 정상 페이지로 바꾼다. 이 함수에도 테스트 5개를 붙였다. 방금 만든 검수 규칙을 바로 써먹은 셈이다.

100개 읽고 거르기에서 10개만 가져오기로

전에는 글 100개를 통째로 읽은 뒤 자바스크립트로 예약 글을 거르고 50개를 잘랐다. 이제는 공개 조건을 SQL로 옮기고 필요한 10개만 가져온다.

// 발행됨 + 예약 시각이 없거나 지남
const visible = and(
	eq(posts.published, true),
	or(isNull(posts.publishAt), lte(posts.publishAt, nowIso()))
);

전체 글 수는 count()로 따로 센다. 본문(content)도 목록에 필요 없어서 조회 컬럼에서 뺐다.

PC와 모바일 나누기는 CSS로

페이지 번호와 더보기 버튼을 둘 다 그려 두고, 화면 폭 760px를 기준으로 하나만 보여 준다. 헤더 메뉴가 쓰는 기준과 같다.

.pager-more { display: none; }
@media (max-width: 760px) {
	.pager-pages { display: none; }
	.pager-more { display: inline-flex; width: 100%; }
}

더보기로 붙는 글은 등장 애니메이션 지연이 10번째부터 길어지는 문제가 있었다. 글 번호를 페이지 크기로 나눈 나머지(i % 10)로 지연을 계산해서, 새로 붙는 10개도 처음부터 차례로 나타나게 했다.

결과

배포 전에 검수를 돌렸다.

0 ERRORS
Test Files  4 passed (4)
     Tests  19 passed (19)
✔ done

운영 사이트에서도 /는 10개, ?page=2는 다음 10개, ?from=1&page=2는 20개가 나오는 것을 확인했다.

남은 것

  • 테스트는 아직 순수 함수만 다룬다. 폼 처리, DB 조회, 실제 브라우저 동작(E2E)은 검사하지 않는다.
  • 검수 규칙은 CLAUDE.md에 적은 약속이라 강제되지는 않는다. 작업이 끝날 때 자동으로 돌게 하려면 Claude Code의 Stop hook을 걸면 된다.
  • 페이지 번호를 전부 나열하고 있어서, 페이지가 20개를 넘으면 중간을 …로 줄이는 방식으로 바꿔야 한다.

댓글 0개

아직 댓글이 없습니다. 첫 댓글을 남겨보세요.

댓글 쓰기

← 목록으로 돌아가기