main-logo

협업을 위한 Next.js 프로젝트 환경 세팅

팀프로젝트에서 환경 세팅 해본 경험을 정리하여 공유합니다.

profile
GNA
2025년 06월 17일 · 0 분 소요

들어가며

이번에 Next.js App Router 기반의 새로운 팀 프로젝트를 시작하면서 처음으로 환경 세팅을 해보게 되었습니다. 이미 잘 세팅된 환경에서만 작업해본 경험 뿐이라 막막했지만, 팀원들의 가이드와 도움을 받아 시도할 수 있었습니다.

 

Next.js 프로젝트 생성

우선 Next.js의 App Router 방식을 사용해 프로젝트를 생성했고, TypeScript, ESLint, Tailwind CSS 등의 옵션도 함께 선택했습니다.

npx create-next-app@latest

설치 옵션:

  • TypeScript: Yes
  • ESLint: Yes
  • Tailwind CSS: 필요 시 Yes
  • App Router: Yes
  • Turbopack: Yes

이렇게 하고 나니 기본적인 프로젝트 구조가 잡혔고,
그다음 단계로 팀 코드 환경을 통일하는 작업에 들어갔습니다.

 

Node 버전 통일

팀원들마다 Node 버전이 다를 수 있어서, 나중에 의존성 충돌이 생길까 봐
처음부터 .nvmrc를 만들어 팀의 Node 버전(22.14.0)을 명시해두었습니다.
(echo "22.14.0" > .nvmrc 명령어로 작성하거나, 수동으로 입력해도 됩니다.)

이렇게 하면 nvm use 같은 버전을 쉽게 맞출 있고,
package-lock.json 충돌을 예방할 있습니다. 🙂

 

Prettier, Stylelint, ESLint, Husky 단계적 세팅

처음엔 Prettier부터 시작해서, Stylelint, ESLint, Husky까지 순차적으로 세팅했습니다.

 

- Prettier
자동 코드 포맷터.
세미콜론, 들여쓰기, 따옴표 등 코드 스타일을 자동으로 맞춰주는 도구.
“코드의 모양(스타일)”을 통일시켜서 협업 때 불필요한 수정 충돌을 방지!

// .prettierrc
{
  "trailingComma": "all",
  "tabWidth": 2,
  "semi": true,
  "singleQuote": true,
  "printWidth": 100,
  "endOfLine": "auto",
  "useTabs": false,
  "arrowParens": "always",
  "htmlWhitespaceSensitivity": "ignore"
}

 

- Stylelint
CSS, SCSS 등의 스타일 코드 검사기.
잘못된 스타일 문법이나 일관성 없는 코드를 잡아주며,
CSS도 팀 규칙에 맞게 쓰도록 도와줍니다.

// .stylelintrc.json

{
  "extends": [
    "stylelint-prettier",
    "stylelint-config-standard-scss",
    "stylelint-config-idiomatic-order"
  ],
  "plugins": ["stylelint-scss", "stylelint-order"],
  "rules": {
    "scss/no-global-function-names": null,
    "function-url-quotes": "never",
    "property-no-vendor-prefix": null,
    "selector-pseudo-class-no-unknown": null,
    "no-descending-specificity": null,
    "order/properties-order": [
      {
        "groupName": "positioning",
        "emptyLineBefore": "never",
        "properties": ["position", "top", "right", "bottom", "left", "z-index"]
      },
      {
        "groupName": "box-model",
        "emptyLineBefore": "never",
        "properties": [
          "display",
          "flex-direction",
          "flex-wrap",
          "justify-content",
          "align-items",
          "align-self",
          "order",
          "width",
          "height",
          "margin",
          "margin-top",
          "margin-right",
          "margin-bottom",
          "margin-left",
          "padding",
          "padding-top",
          "padding-right",
          "padding-bottom",
          "padding-left",
          "border",
          "border-width",
          "border-style",
          "border-color",
          "border-radius",
          "box-sizing"
        ]
      },
      {
        "groupName": "typography",
        "emptyLineBefore": "never",
        "properties": [
          "font",
          "font-family",
          "font-size",
          "font-weight",
          "font-style",
          "line-height",
          "letter-spacing",
          "text-align",
          "text-decoration",
          "text-transform",
          "color"
        ]
      },
      {
        "groupName": "visual",
        "emptyLineBefore": "never",
        "properties": [
          "background",
          "background-color",
          "background-image",
          "background-repeat",
          "background-position",
          "background-size",
          "background-clip",
          "background-origin",
          "box-shadow",
          "opacity",
          "outline",
          "outline-width",
          "outline-style",
          "outline-color",
          "outline-offset",
          "filter",
          "mix-blend-mode",
          "isolation",
          "backdrop-filter"
        ]
      },
      {
        "groupName": "misc",
        "emptyLineBefore": "never",
        "properties": [
          "cursor",
          "pointer-events",
          "visibility",
          "clip",
          "clip-path",
          "overflow",
          "overflow-x",
          "overflow-y",
          "transform",
          "transform-origin",
          "transition",
          "transition-property",
          "transition-duration",
          "transition-timing-function",
          "transition-delay",
          "animation",
          "animation-name",
          "animation-duration",
          "animation-timing-function",
          "animation-delay",
          "animation-iteration-count",
          "animation-direction",
          "animation-fill-mode",
          "animation-play-state"
        ]
      }
    ],
    "order/properties-alphabetical-order": null
  }
}

 

- ESLint
JavaScript/TypeScript 코드 검사기.
문법 오류부터 코드 품질, 팀 규칙까지 전반적으로 검사!
airbnb나 prettier 등과 함께 팀 스타일을 유지하기 위한 룰을 쉽게 관리할 수 있습니다.

// .eslintrc.json

{
  "extends": ["airbnb", "airbnb-typescript", "next/core-web-vitals", "plugin:prettier/recommended"],
  "parserOptions": {
    "project": "./tsconfig.json"
  },
  "rules": {
    "semi": ["error", "always"],
    "quotes": ["error", "single"],
    "array-callback-return": "off",
    "react/require-default-props": "off",
    "react/no-array-index-key": "off",
    "react/function-component-definition": [2, { "namedComponents": "arrow-function" }],
    "react/jsx-props-no-spreading": "off",
    "jsx-a11y/click-events-have-key-events": 0,
    "jsx-a11y/label-has-associated-control": 0,
    "jsx-a11y/no-noninteractive-element-interactions": 0,
    "jsx-a11y/no-static-element-interactions": 0,
    "jsx-a11y/mouse-events-have-key-events": 0,
    "jsx-a11y/anchor-is-valid": 0,
    "react/no-unstable-nested-components": 0,
    "react/react-in-jsx-scope": 0,
    "react/prefer-stateless-function": 0,
    "react/jsx-filename-extension": 0,
    "react/jsx-one-expression-per-line": 0,
    "no-nested-ternary": 0,
    "no-undef": "off",
    "no-useless-escape": "off",
    "react-hooks/rules-of-hooks": "error",
    "react-hooks/exhaustive-deps": "error",
    "import/prefer-default-export": "off",
    "prettier/prettier": [
      "error",
      {
        "endOfLine": "auto",
        "useTabs": false
      }
    ],
    "indent": ["error", 2, { "SwitchCase": 1 }],
    "@typescript-eslint/no-empty-interface": "off",
    "no-empty-interface": "off",
    "react/button-has-type": "off",
    "import/extensions": "off"
  }
}

예상 못한 문제: eslint airbnb 룰 충돌
처음엔 airbnb 룰을 기본으로 쓰려 했는데, Next.js가 사용하는 eslint 9 버전과 airbnb가 지원하는 8 버전이 충돌나서 한참 헤맸습니다.

결국 eslint를 8로 내리고 airbnb, airbnb-typescript를 다시 설치!
airbnb 룰에서 필요없는 것들은 덮어써서 팀 스타일에 맞췄습니다.

 

- Husky
Git hook 관리 도구.
커밋하기 전에 lint나 테스트가 통과됐는지 자동으로 검사.
“커밋 전에 자동으로 검사/포맷”을 실행해서 팀 코드 품질을 지켜줍니다.

 

마치며

Prettier, Stylelint, ESLint, Husky까지 순서대로 세팅하면서,
팀 코드 스타일을 단계적으로 통일하고, 협업 환경을 정비할 수 있었습니다.
작업 중에는 예상 못한 충돌이나 작은 실수도 있었지만,
그 헤매는 과정 덕분에 그냥 지나쳤을 것들을 한번 더 볼 수 있었던 것 같습니다.

차근차근 해결하면서 환경 세팅이 단순히 도구 설치가 아니라, 협업 문화를 만드는 출발점이라는 걸 느꼈습니다.
아직 공부가 더 필요하지만, 앞으로는 예전보다 겁먹지 않고 환경 세팅을 더 자연스럽게 진행할 수 있을 것 같습니다! 

많이 도와주고 기다려준 팀원들 늘 감사합니다.🫶