Skip to content

코딩 컨벤션

dictionary-app이 실제로 지키는 코딩 컨벤션이다. Prettier/oxlint가 자동으로 강제하는 규칙과, 코드 작성·리뷰 시 직접 신경 써야 하는 규칙을 구분한다.

.prettierrc.cjs
module.exports = {
bracketSpacing: false,
singleQuote: true,
trailingComma: 'all',
arrowParens: 'always',
semi: false,
printWidth: 90,
tabWidth: 2,
useTabs: false,
endOfLine: 'lf',
}

세미콜론 없음, 싱글쿼트, trailing comma, printWidth 90 — pre-commit 훅의 lint-staged가 스테이징된 파일에 자동 적용한다(자세한 내용은 깃훅 가이드 참고).

.oxlintrc.json
{
"rules": {
"no-debugger": "error",
"no-var": "error",
"eqeqeq": "error",
"curly": ["error", "all"],
"dot-notation": "error",
"react/rules-of-hooks": "error",
"react/exhaustive-deps": "error",
"react/only-export-components": "warn",
"typescript/no-unused-expressions": ["error", {"allowShortCircuit": true}]
}
}
  1. no-var/eqeqeq/curly/dot-notation처럼 흔한 JS 실수를 막는 규칙과, react/rules-of-hooks/ react/exhaustive-deps처럼 훅 오용을 막는 React 전용 규칙을 함께 쓴다.
  2. pnpm lint로 직접 실행하거나, pre-commit 훅의 lint-staged(oxlint --fix)가 스테이징된 .ts/.tsx 파일에 자동 적용한다.

컴포넌트/함수 작성 규칙 (도구가 강제하지 않음, 직접 신경 써야 함)

Section titled “컴포넌트/함수 작성 규칙 (도구가 강제하지 않음, 직접 신경 써야 함)”
  1. function 대신 화살표 연산자를 사용한다.
  2. 이름 붙여 정의하는 함수(export const/const)는 한 줄로 표현 가능해도 암묵적 반환(=> expr) 대신 중괄호 + return을 쓴다.
    // ❌
    export const isNull = <T,>(value: T) => value === null
    // ✅
    export const isNull = <T,>(value: T) => {
    return value === null
    }
    나중에 조건 분기나 로그 한 줄이 추가돼도 화살표 스타일을 바꿀 필요가 없다. 배열 메소드 등에 즉석으로 넘기는 짧은 인라인 콜백(.map((i) => i))에는 적용하지 않는다 — 이름 붙여 정의하는 함수에 한정된 규칙이다.
  3. use client 구문은 사용하지 않는다.
  4. 모델을 알 수 있거나 제네릭을 쓸 수 있는 경우 제네릭을 적극 활용한다.
  5. 컴포넌트 이름은 파스칼 케이스, 확장자는 .tsx(HTML을 반환하지 않으면 .ts 허용)로 하고, 파일명은 컴포넌트 이름과 동일하게 짓는다.
  6. export default를 쓰지 않는다 — 컴포넌트/함수 정의 시 이름 왼쪽에 export를 붙여, 임포트할 때 뭉쳐서 쓸 수 있게 한다.
  7. 프롭으로 주입하는 이벤트 핸들러 이름은 handle 접두사를 쓴다 — onClick이 아니라 handleClick.
  8. 이벤트 핸들러는 JSX에 인라인 화살표 함수(onClick={() => ...})로 쓰지 않고, 컴포넌트 함수 바디에 handleClick 같은 이름의 함수로 분리한 뒤 참조한다. 리스트 렌더링처럼 인자를 즉시 바인딩해야 하는 경우에도 로직 자체는 이름 붙은 핸들러에 두고, JSX에서는 onClick={() => handleSelect(item.id)}처럼 인자만 넘기는 최소한의 래핑만 허용한다.

조건절·반복문은 한 줄이어도 중괄호로 감싼다

Section titled “조건절·반복문은 한 줄이어도 중괄호로 감싼다”
  1. if/else/for/while은 본문이 한 줄이어도 항상 중괄호({})로 감싼다.
    // ❌
    if (x > 0) return x
    // ✅
    if (x > 0) {
    return x
    }
  2. oxlint curly 규칙(["error", "all"])이 강제하고, pre-commit 훅의 oxlint --fix가 자동으로 고친다.
  3. 조건이 나중에 추가되거나 로그 한 줄이 붙어도 중괄호를 새로 씌울 필요가 없다. 코드베이스 전체에서 조건절 모양도 하나로 통일된다.
  1. interface 대신 type을 쓴다. pnpm gen:api-types로 생성하는 src/types/schema.ts는 생성 결과를 그대로 두므로 이 규칙의 예외이다.
  2. 파일 하나에 서로 관련된 타입을 여러 개 정의할 수 있다. 파일명은 다음 기준으로 정한다.
    • 타입 하나만 정의하는 파일: 타입 이름과 같은 파스칼 케이스(예: ColumnNameResult.ts)
    • 관련 타입 여러 개를 주제로 묶는 파일: 소문자 단일 명사(예: date.ts가 DatePattern·DatePatternName을 export)
  3. 서로 무관한 타입을 한 파일에 섞지 않는다. 한 주제로 묶이지 않으면 파일을 나눈다.
  4. 이 규칙은 최상위 src/types와 페이지 전용 models 디렉토리에 똑같이 적용된다.
    src/types/date.ts
    export type DatePattern = {
    dateFormat: string
    regExpPattern: RegExp
    dayjsFormat: string
    }
  1. src/utils/의 파일은 두 성격 중 하나를 따른다.
  2. topic 파일: 관련된 헬퍼 함수 여러 개를 주제 단위로 묶는다. 파일명은 소문자 단일 명사로 짓는다(예: string.ts가 toCamelCase·formatNumber 등을 export).
  3. singleton 파일: 값이나 인스턴스 하나만 export한다. 파일명은 export 이름과 같은 camelCase로 짓는다(예: queryClient.ts가 queryClient를 export). 이런 파일에는 관련 없는 헬퍼 함수를 함께 두지 않는다.
  4. 페이지 하위 컴포넌트끼리 공유하는 헬퍼 함수의 위치는 프로젝트 디렉토리 구성 참고.
  1. 스타일은 별도 CSS 파일, styled-components, sx prop, 인라인 style 대신 Chakra UI v3의 chakra() 팩토리로 작성한다.
  2. 스타일은 컴포넌트와 같은 디렉토리의 Styled{컴포넌트 이름}.ts 파일 하나에 모은다. 한 컴포넌트가 쓰는 styled 요소는 여러 개여도 파일을 쪼개지 않는다.
  3. chakra()로 만든 태그의 이름은 Styled{태그 역할} 형식으로 짓는다(예: StyledCard, StyledHeading). {태그 역할}은 그 요소가 화면에서 맡는 역할이다.
  4. 감싸는 대상에 따라 두 패턴 중 하나를 쓴다.
    • 'div' 같은 순수 HTML 태그나 motion.div처럼 스타일 단축 prop을 받지 못하는 대상은 2번째 인자(recipe)의 base에 스타일을 정의한다.
    • Heading/Text/Flex처럼 이미 Chakra 컴포넌트인 대상은 recipe를 빈 객체({})로 두고, 3번째 인자의 defaultProps로 자주 반복되는 prop 조합만 기본값으로 지정한다.
    pages/word/column/StyledColumn.ts
    export const StyledCard = chakra('div', {
    base: {
    bg: 'bg',
    borderWidth: '1px',
    borderColor: 'border',
    borderRadius: 'lg',
    boxShadow: 'xs',
    p: 5,
    },
    })
    export const StyledHeading = chakra(
    Heading,
    {},
    {defaultProps: {size: 'lg', color: 'brand.fg', mb: 4}},
    )
  5. chakra() 호출 결과를 컴포넌트 이름으로 그대로 export하지 않는다. ComponentName.tsx는 항상 JSX를 반환하는 실제 컴포넌트여야 하고, 스타일 태그는 Styled*.ts에 두고 렌더링만 한다.
    layout/footer/Footer.tsx
    import {StyledBar, StyledCopyright} from './StyledFooter'
    export const Footer = () => {
    return (
    <StyledBar>
    <StyledCopyright>© {new Date().getFullYear()} mintchoco.biz</StyledCopyright>
    </StyledBar>
    )
    }
  6. 색상은 시맨틱 토큰(fg, fg.muted, bg, border 등)을 기본으로 쓴다. 로그인 화면처럼 라이트/다크 테마와 무관하게 같은 톤을 유지해야 하는 화면에만 brand.700, red.600 같은 고정 컬러 토큰을 쓴다.
  1. 단축어들을 언더바(_)로 이어 붙이고 도메인을 결합한 최종 문자열은 코드에서 columnName으로 부른다. 과정이 아니라 실제 DB 컬럼명 그 자체를 뜻하는 값이기 때문이다.
  2. 이 값을 만드는 함수는 generateColumnName(또는 buildColumnName)으로 짓는다.
  3. 토큰 목록·도메인 접미사·최종 컬럼명을 함께 담는 타입은 ColumnNameResult(또는 GeneratedColumnName)로 짓는다. 현재 코드는 pages/word/column/models/ColumnNameResult.ts에 있다.
  1. .env/.env.* 파일은 AI가 읽거나 고치지 못하도록 .claude/settings.json의 deny 규칙으로 막혀 있다.
  2. dictionary-api(백엔드) 소스는 API 스펙 확인 같은 참조용으로만 쓴다. 백엔드 코드 수정이 필요하면 dictionary-api 저장소에서 따로 진행한다.
  3. 다른 개발 규칙은 해당 문서를 따른다.