© 2025 Sejin Cha. All rights reserved.
Built with Next.js, deployed on Vercel
⚙️
스윗미 초기 세팅 스택 State management (context API )
React vite 사용 (노드 버전 제한 있음)
14.18+ 또는 16+ 의 Node.js를 요구 cra는 모듈 번들러로 webpack 사용 → 느림 vite는 esbuild를 기반 → 100배 빠름 시작: yarn create vite swithme —template react
절대 경로 설정 vite.config.js
파일의 defineConfig
에 아래 코드 추가
Emotion yarn add @emotion/react @emotion/styled
앱을 cra로 만들지 않았으므로 CRACO(Create-React-App Configuration Override: CRA에 config 설정을 덮어쓰기 위한 패키지)를 설치하거나 할 필요가 없음 StoryBook yarn add storybook
yarn sb init
Prettier / Eslint yarn add -D eslint prettier eslint-config-prettier eslint-plugin-import eslint-plugin-jsx-a11y eslint-plugin-prettier eslint-plugin-react eslint-plugin-react-hooks
eslint-plugin-prettier
: prettier 의 설정 중 eslint 의 설정과 충돌이 나는 설정들을 비활성화하는 플러그인eslint-config-prettier
: prettier의 규칙을 eslint에 적용시킬 수 있게 하는 플러그인건열님이 예전에 사용하신 rules
📌 약간 적용이 잘 안되는 부분이 좀 있는 것 같긴 하다..
차근차근 수정해봅시다..
글로벌 스타일, 폰트 yarn add emotion-reset
폰트는 Pretendard 사용
index.html
head 태그에 추가
폴더 구조
components
domain: postItem, postdetail과 같이 좀 더 디테일한 부분 template: 항상 있어야 하는 부분(ex. 헤더)
이슈 템플릿 / 깃허브 템플릿
이슈 템플릿 (Settings에서 설정 가능) .github
폴더 밑에 pull_request_template.md
파일 생성 후 템플릿 작성
환경변수 설정
yarn add dotenv
.env
파일 생성
vite.cofig.js
코드 다음과 같이 수정
import { defineConfig } from "vite";
import react from "@vitejs/plugin-react";
import * as path from "path"; // path import
export default defineConfig({
plugins: [react()],
// resolve 추가
resolve: {
alias: {
"@": path.resolve(__dirname, "./src"),
},
},
});
{
"env": {
"browser": true,
"es6": true
},
"parserOptions": {
"ecmaVersion": "latest",
"sourceType": "module"
},
"plugins": ["react"],
"extends": ["react-app", "eslint:recommended", "plugin:react/recommended", "plugin:prettier/recommended", "prettier"],
"rules": {
"linebreak-style": 0,
"object-curly-newline": 0,
"no-unused-vars": "warn",
"no-use-before-define": 1,
"indent": ["error", 2],
"react/react-in-jsx-scope": "off",
"react/jsx-filename-extension": [1, { "extensions": [".jsx"] }],
"import/prefer-default-export": "off",
"jsx-quotes": ["error", "prefer-single"],
"no-console": 0,
"no-alert": 0,
"react/jsx-props-no-spreading": 0,
"react/prop-types": 0
}
}
"linebreak-style": 0,
"object-curly-newline": 0,
"no-unused-vars": "warn",
"no-use-before-define": 1,
"indent": ["error", 4],
"react/jsx-indent": [2, 4, { "checkAttributes": true, "indentLogicalExpressions": true }],
"react/jsx-indent-props": [2, 4],
"react/react-in-jsx-scope": "off",
"react/jsx-filename-extension": [1, { "extensions": [".js", ".jsx"] }],
"import/prefer-default-export": "off",
"jsx-quotes": [2, "prefer-single"],
"no-console": 0,
"no-alert": 0,
"jsx-a11y/no-noninteractive-element-interactions": [
"error",
{
"handlers": [
"onClick",
"onMouseDown",
"onMouseUp",
"onKeyPress",
"onKeyDown",
"onKeyUp"
]
}
],
"react/jsx-props-no-spreading": 0,
"react/prop-types": 0
{
"singleQuote": true, // single 쿼테이션 사용 여부
"trailingComma": "all", // 여러 줄을 사용할 때, 후행 콤마 사용 방식
"semi": true, // 세미콜론 사용 여부
"useTabs": false, // 탭 사용 여부
"tabWidth": 2, // 탭 너비
"printWidth": 120, // 줄 바꿈 할 폭 길이
"arrowParens": "always", // 화살표 함수 괄호 사용 방식
"bracketSameLine": true // 객체 리터럴에서 괄호에 공백 삽입 여부
}
import { Global, css } from '@emotion/react';
import emotionReset from 'emotion-reset';
const style = css`
${emotionReset}
html {
font-size: 62.5%;
}
html, body {
width: 100%;
height: 100%;
}
* {
box-sizing: border-box;
}
body,
button,
input {
font-family: 'Pretendard Variable', Pretendard, -apple-system, BlinkMacSystemFont, system-ui, Roboto,
'Helvetica Neue', 'Segoe UI', 'Apple SD Gothic Neo', 'Noto Sans KR', 'Malgun Gothic', 'Apple Color Emoji',
'Segoe UI Emoji', 'Segoe UI Symbol', sans-serif;
}
select,
input,
button,
textarea {
border: 0;
outline: 0;
}
a {
text-decoration: none;
}
button {
cursor: pointer;
background-color: transparent;
}
`;
const GlobalStyle = () => {
return <Global styles={style} />;
};
export default GlobalStyle;
<link
rel="stylesheet"
as="style"
crossorigin
href="https://cdn.jsdelivr.net/gh/orioncactus/pretendard@v1.3.6/dist/web/static/pretendard-dynamic-subset.css" />
📦src
┣ 📂assets
┣ 📂components
┃ ┣ 📂base
┃ ┣ 📂domain
┃ ┣ 📂templat
┣ 📂contex
┣ 📂hooks
┣ 📂page
┣ 📂stories
┃ ┣ 📂components
┃ ┣ 📂hooks
┣ 📂styles
┃ ┗ 📜globalStyle.jsx
┣ 📂utils
┃ ┗ 📜api.js
┣ 📜App.jsx
┗ 📜main.jsx
import Menu from '@components/domain/Menu';
const DefaultTemplate = ({ children }) => {
return (
<div>
<Menu />
<main>{children}</main>
</div>
);
};
export default DefaultTemplate;
// 기능 템플릿
## Description
## Progress
- [ ] To do
- [ ] To do
- [ ] To do
// 버그 픽스 템플릿
## Reproduce
> 버그가 어디에서 어떻게 발생했는지 작성
## Screenshot
## Environment
> 어떤 환경(OS), 브라우저
## ⛓ 관련 이슈
- close #issue_number
## 📝 작업 내용
- [ ] 구현한 기능
- [ ] 스타일링 등
## 📍 PR Point
- 리뷰어가 알아야 하는 부분
- 우려되는 부분
- 궁금한 점
## 📷 스크린샷
API_END_POINT=http://kdt.frontend.3rd.programmers.co.kr
PORT=5004
import { defineConfig, loadEnv } from 'vite';
import react from '@vitejs/plugin-react';
import * as path from 'path';
export default defineConfig(({ mode }) => {
const env = loadEnv(mode, process.cwd(), '');
return {
define: {
__APP_ENV__: env.APP_ENV,
},
plugins: [react()],
resolve: {
alias: {
'@': path.resolve(__dirname, './src'),
},
},
};
});