본문으로 바로가기
Seungpyo Suh.01 · Principles

Code.

sspzoa.io 코드 컨벤션. UI 카피는 한국어(lang="ko"), 식별자·주석·커밋은 영어. 게이트는 bun run lint 하나. 비주얼 토큰은 Design System.

Principles

에이전트·기여자가 같은 결정을 반복하지 않도록 고정한 규칙.

  • Server by default인터랙션이 필요할 때만 "use client".
  • Named exportsNext.js 엔트리(page/layout/error/metadata)만 default export.
  • No hand-rolled memoReact Compiler 사용. useMemo / useCallback / React.memo 금지에 가깝게.
  • Tokens only임의 px·hex 금지. 스케일은 /design-system.
  • Bun onlynpm / yarn / pnpm 쓰지 않음. bun.lock 커밋.
  • No drive-by docs요청 없이 README·주석 추가하지 않음. 주석도 기본적으로 넣지 않음.

Stack

고정 선택. 대체 도구를 새로 들이지 않음.

  • FrameworkNext.js 16 App Router
  • UIReact 19 + React Compiler
  • LanguageTypeScript 5 strict · @/* → ./src/*
  • StylingTailwind CSS v4 · token-only
  • Lint / formatBiome 2.2 (no ESLint, no Prettier)
  • ValidationZod 4
  • Iconslucide-react
  • CMSNotion API 2025-09-03
  • Package managerBun · Node ≥ 20
  • Analytics@vercel/analytics · speed-insights

Commands

변경 완료 전 항상 lint. 프로젝트의 유일한 로컬 게이트.

package scripts
bun install
bun dev
bun run build
bun start
bun run lint        # biome check
bun run lint:fix   # biome check --write
bun run format      # biome format --write

Structure

features = 페이지 전용. shared = 재사용. app = 라우트 엔트리.

src/
src/
  app/                 # routes, layout, metadata
  features/            # page-specific UI + sections
    sections/          # async Server Component sections
  shared/
    ui/                # presentational primitives
    lib/               # env, notion, portfolio-data, errors
    utils/             # formatDate, formatPeriod
    markdown/          # server-safe markdown-lite
    schemas.ts         # Zod + z.infer types
    types.ts           # re-exports
  • features/sections섹션마다 데이터 fetch + 에러 바운더리 소유.
  • shared/ui도메인 몰라도 쓸 수 있는 프레젠테이션 프리미티브.
  • shared/libenv, Notion 클라이언트, portfolio-data, errors.

TypeScript

strict. 스키마가 타입의 정본(source of truth).

  • stricttrue
  • paths@/* → "./src/*"
  • Entity typesz.infer from shared/schemas.ts
  • Re-exportsshared/types.ts

Imports

  • Path alias상대 경로 대신 @/….
  • type imports타입만 쓸 때 import type.
  • Organize importsBiome assist가 정리. 수동 정렬 금지.

React

RSC 기본. 클라이언트는 스크롤 관찰·토글 등 상태/브라우저 API가 필요할 때만.

Server vs client (today)

  • Serversections/*, Description, pages, layout
  • Clientnav, side-project-toggle, collapsible

Section error pattern

features/sections/*
export async function XSection({ index, id }: SectionComponentProps) {
  try {
    const items = await fetchX();
    if (items.length === 0) return null;
    return (
      <Section id={id} title="X" index={index} count={items.length}>
        …
      </Section>
    );
  } catch (error) {
    console.error("[XSection]", error);
    return (
      <Section id={id} title="X" index={index}>
        <p className="text-content-standard-secondary text-label">
          {getErrorMessage(error)}
        </p>
      </Section>
    );
  }
}

getErrorMessage @/shared/lib/errors에서 import. 로컬 재정의 금지. empty → null.

Images

  • next/image항상 width / height / sizes, draggable={false}. 장식 이미지는 alt="".
  • Remote hosts새 호스트는 next.config.ts remotePatterns와 CSP img-src 둘 다.

Format

Biome 단일 툴체인. CSS는 Biome 제외 — globals.css는 수동 포맷.

  • indent2 spaces
  • lineWidth120
  • quotesdouble (JSX too)
  • semicolonsalways
  • trailingCommasall (JSON: none)
  • bracketSameLinetrue (JSX > on last prop line)
  • arrowParenthesesalways
  • lineEndinglf
  • useSortedClasseswarn · lint:fix sorts
  • useArrowFunctionerror
  • noUnusedImportserror
JSX bracketSameLine
<Button
  text="Save"
  onClick={handleSave}
/>

Naming

영어 식별자. 파일은 kebab-case.

  • Fileskebab-case.tsx · aboutme.tsx, project-card.tsx
  • ComponentsPascalCase named export · export function Chip
  • HooksuseActiveSection (colocate in feature file OK)
  • FetchersfetchProjects, getPortfolioData
  • ErrorsDataFetchError, DataValidationError, NotionApiError
  • Env varsNOTION_TOKEN, *_DATA_SOURCE_ID (UUID)

Styling

디자인 토큰만. Layer 1 --solid-* 는 globals.css 전용.

  • Semantic classestext-content-standard-primary, p-spacing-500, rounded-radius-md, duration-fast.
  • No theme toggleprefers-color-scheme only. Providers는 pass-through.
  • Motion상태 변화(hover/focus/active)만. 스크롤 리빌 장식 금지. reduced-motion 존중.
  • tracking-wider / widestTailwind 빌트인 사용 허용 (토큰 위반 아님).
  • Focus ringfocus-visible:ring-2 focus-visible:ring-core-accent/50 focus-visible:ring-offset-2 focus-visible:ring-offset-background-standard-primary

전체 스케일·컴포넌트 쇼케이스 → /design-system

Data

Notion이 소스 오브 트루스. 요청마다 live fetch — 캐시 없음(force-dynamic + no-store).

pipeline
.env.local
  → env.ts (Zod at import)
  → portfolio-data.ts → notion.ts
  → schema.parse()
  → Server Component section
  • aboutMe requiredgetPortfolioData에서 aboutMe 실패는 전파. 나머지 섹션은 []로 degrade.
  • Error kindsDataFetchError (4xx → config), DataValidationError (Zod).
  • DatesISO → YYYY.MM via formatDate / formatPeriod.
  • No caching by design신선도 트레이드오프 없이 캐시 추가하지 말 것.

A11y & security

새 마크업은 접근 가능해야 함. CSP·보안 헤더는 next.config.ts.

  • Skip linklayout의 #main-content 유지.
  • aria-current / aria-expanded네비·Collapsible 패턴 유지.
  • dangerouslySetInnerHTMLJSON-LD Person 스키마 한 곳만. 새로 도입 금지.
  • Secrets로그·커밋에 토큰 금지. env는 Zod로 import-time 검증.
  • CSP외부 script/style/font/img/connect 추가 시 해당 *-src 갱신.