Next.js ํ๋ก์ ํธ ํ๊ฒฝ ์ค์
npx create-next-app@latest .
2. ํ๋ก์ ํธ ์์ฑ ์ต์
โ Would you like to use TypeScript? ... Yes
โ Which linter would you like to use? ยป ESLint
โ Would you like to use Tailwind CSS? ... Yes
โ Would you like your code inside a ` src/` directory? ... Yes
โ Would you like to use App Router? (recommended) ... Yes
โ Would you like to use Turbopack? (recommended) ... No
โ Would you like to customize the import alias (` @/* ` by default)? ... Yes
โ What import alias would you like configured? ... @/*
3. Tailwind ํ๊ฒฝ ์ค์
package.json ์์ tailwind ๋ฒ์ ํ์ธ
์๋ ์ฒ๋ผ ๋ฒ์ ์ด ์ต์ 4.x ๋ฒ์ ํ์ธ
3.1. postcss.config.mjs ์ค์ ํ์ธ
const config = {
plugins : [ '@tailwindcss/postcss' ] ,
} ;
export default config ;
3.2. tailwind.config.ts ํ์ผ ์์ฑ
Tailwind ๊ฒฝ๋ก ๋ฐ Theme ์ค์ , Plugin ์ถ๊ฐ, Dark Mode, CSS ๋ณ์ ์ฐ๊ฒฐ
import type { Config } from 'tailwindcss' ;
const config : Config = {
// 1. ์ปจํ
์ธ ๊ฒฝ๋ก: Tailwind๊ฐ ํด๋์ค๋ฅผ ์ฐพ์ ํ์ผ ๊ฒฝ๋ก
content : [
'./src/pages/**/*.{js,ts,jsx,tsx,mdx}' ,
'./src/components/**/*.{js,ts,jsx,tsx,mdx}' ,
'./src/app/**/*.{js,ts,jsx,tsx,mdx}' ,
] ,
theme : {
extend : {
// 2. ์ปค์คํ
์์: CSS ๋ณ์์ ์ฐ๊ฒฐ๋ ์์ ์ ์
colors : {
background : 'var(--background)' ,
foreground : 'var(--foreground)' ,
} ,
// 3. ์ปค์คํ
ํฐํธ: ํ๋ก์ ํธ์์ ์ฌ์ฉํ ํฐํธ ํจ๋ฐ๋ฆฌ ์ ์
fontFamily : {
sans : [ 'var(--font-geist-sans)' , 'system-ui' , 'sans-serif' ] ,
mono : [ 'var(--font-geist-mono)' , 'monospace' ] ,
} ,
} ,
} ,
// 4. ํ๋ฌ๊ทธ์ธ: ์ถ๊ฐ ๊ธฐ๋ฅ์ ์ํ ํ๋ฌ๊ทธ์ธ ๋ฐฐ์ด
plugins : [ ] ,
} ;
export default config ;
tailwind.config.ts ๋ด์ฉ ์ถ๊ฐ
import type { Config } from 'tailwindcss' ;
const config : Config = {
content : [
'./src/pages/**/*.{js,ts,jsx,tsx,mdx}' ,
'./src/components/**/*.{js,ts,jsx,tsx,mdx}' ,
'./src/app/**/*.{js,ts,jsx,tsx,mdx}' ,
] ,
darkMode : 'class' , // ๋คํฌ ๋ชจ๋ ์ค์
theme : {
extend : {
// ๋ธ๋๋ ์์ ์์คํ
colors : {
primary : {
50 : '#eff6ff' ,
100 : '#dbeafe' ,
200 : '#bfdbfe' ,
300 : '#93c5fd' ,
400 : '#60a5fa' ,
500 : '#3b82f6' , // ๊ธฐ๋ณธ primary ์์
600 : '#2563eb' ,
700 : '#1d4ed8' ,
800 : '#1e40af' ,
900 : '#1e3a8a' ,
950 : '#172554' ,
} ,
// ์ํ ์์
success : {
/* ๋
น์ ๊ณ์ด */
} ,
warning : {
/* ๋
ธ๋์ ๊ณ์ด */
} ,
error : {
/* ๋นจ๊ฐ์ ๊ณ์ด */
} ,
} ,
// ํ์ดํฌ๊ทธ๋ํผ ์์คํ
fontSize : {
xs : [ '0.75rem' , { lineHeight : '1rem' } ] ,
sm : [ '0.875rem' , { lineHeight : '1.25rem' } ] ,
base : [ '1rem' , { lineHeight : '1.5rem' } ] ,
// ... ๋ ๋ง์ ํฌ๊ธฐ
} ,
// ์ ๋๋ฉ์ด์
์์คํ
animation : {
'fade-in' : 'fadeIn 0.5s ease-in-out' ,
'slide-in' : 'slideIn 0.3s ease-out' ,
'bounce-in' : 'bounceIn 0.6s ease-out' ,
} ,
} ,
} ,
plugins : [ ] ,
} ;
export default config ;
import type { Config } from 'tailwindcss' ;
const config : Config = {
content : [
'./src/app/**/*.{js,ts,jsx,tsx,mdx}' ,
'./src/components/**/*.{js,ts,jsx,tsx,mdx}' ,
'./src/pages/**/*.{js,ts,jsx,tsx,mdx}' ,
] ,
theme : {
extend : {
colors : {
// Linguavibe Brand Colors
sky : {
50 : '#f0f9ff' ,
100 : '#e0f2fe' ,
200 : '#bae6fd' ,
300 : '#7dd3fc' ,
400 : '#38bdf8' ,
500 : '#0ea5e9' , // Main color
600 : '#0284c7' , // Hover color
700 : '#0369a1' ,
} ,
teal : {
300 : '#5eead4' ,
400 : '#2dd4bf' , // Accent color (vibed)
500 : '#14b8a6' , // Accent hover
} ,
gray : {
50 : '#fafafa' ,
100 : '#f5f5f5' ,
200 : '#e5e7eb' , // Border / line
300 : '#d1d5db' ,
400 : '#9ca3af' ,
500 : '#6b7280' , // Sub text
600 : '#4b5563' ,
700 : '#374151' ,
800 : '#1f2937' , // Main text
900 : '#111827' ,
} ,
stone : {
50 : '#fafaf9' , // Neutral background
} ,
} ,
fontFamily : {
sans : [ 'Inter' , 'Noto Sans KR' , 'sans-serif' ] ,
} ,
borderRadius : {
xl : '0.75rem' , // rounded-xl
'2xl' : '1rem' ,
'3xl' : '1.5rem' ,
} ,
boxShadow : {
sm : '0 1px 2px 0 rgba(0, 0, 0, 0.05)' , // shadow-sm (subtle)
DEFAULT :
'0 1px 3px 0 rgba(0, 0, 0, 0.1), 0 1px 2px 0 rgba(0, 0, 0, 0.06)' ,
md : '0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06)' ,
} ,
maxWidth : {
'2xl' : '42rem' , // Content width
} ,
spacing : {
18 : '4.5rem' ,
} ,
fontSize : {
xs : [ '0.75rem' , { lineHeight : '1.5' } ] ,
sm : [ '0.875rem' , { lineHeight : '1.5' } ] ,
base : [ '1rem' , { lineHeight : '1.6' } ] , // Body text
lg : [ '1.125rem' , { lineHeight : '1.6' } ] ,
xl : [ '1.25rem' , { lineHeight : '1.5' } ] ,
'2xl' : [ '1.5rem' , { lineHeight : '1.4' } ] ,
'3xl' : [ '1.875rem' , { lineHeight : '1.3' } ] ,
'4xl' : [ '2.25rem' , { lineHeight : '1.2' } ] ,
'5xl' : [ '3rem' , { lineHeight : '1.1' } ] ,
} ,
} ,
} ,
plugins : [ ] ,
} ;
export default config ;
Next.js์ ๋ชจ๋ ์ปดํฌ๋ํธ๋ค์ด ์ฐธ์กฐํ๋ ๊ธ๋ก๋ฒ css
/src/app/global.css ์ฐธ์กฐ
๋ฐ๋์ @import๋ css ์ฒซ์ค์ด์ฌ์ผ ํจ.
tailwind 4.x ๋ฒ์ ์ด๋ฏ๋ก @import "tailwindcss";
@import 'tailwindcss' ;
: root {
--background : # ffffff ;
--foreground : # 171717 ;
}
@theme inline {
--color-background : var (--background );
--color-foreground : var (--foreground );
--font-sans : var (--font-geist-sans );
--font-mono : var (--font-geist-mono );
}
/* ๋คํฌ ๋ชจ๋ ์ค์ */
.dark {
--background : # 0a0a0a ;
--foreground : # ededed ;
}
/* ์์คํ
๋คํฌ ๋ชจ๋ ์ง์ */
@media (prefers-color-scheme : dark) {
: root {
--background : # 0a0a0a ;
--foreground : # ededed ;
}
}
/* ๊ธฐ๋ณธ ์คํ์ผ */
* {
box-sizing : border-box;
padding : 0 ;
margin : 0 ;
}
html {
scroll-behavior : smooth;
overflow-x : hidden;
}
body {
background : var (--background );
color : var (--foreground );
font-family : var (--font-geist-sans ), system-ui, sans-serif;
line-height : 1.6 ;
-webkit-font-smoothing : antialiased;
-moz-osx-font-smoothing : grayscale;
}
/* ์คํฌ๋กค๋ฐ ์คํ์ผ๋ง */
::-webkit-scrollbar {
width : 8px ;
}
::-webkit-scrollbar-track {
background : var (--background );
}
::-webkit-scrollbar-thumb {
background : # cbd5e1 ;
border-radius : 4px ;
}
.dark ::-webkit-scrollbar-thumb {
background : # 475569 ;
}
/* ํฌ์ปค์ค ์คํ์ผ */
: focus-visible {
outline : 2px solid # 3b82f6 ;
outline-offset : 2px ;
}
/* ์ ํ ํ
์คํธ ์คํ์ผ */
::selection {
background-color : # 3b82f6 ;
color : white;
}
Prettier๋ ์ฝ๋ ํฌ๋งทํฐ๋ก, ์ผ๊ด๋ ์ฝ๋ ์คํ์ผ์ ์๋์ผ๋ก ์ ์งํจ.
Prettier - Code formatter ์ค์น ํ์ฅํ๋ก๊ทธ๋จ ํ์
npm install --save-dev prettier
4.2. /.prettierrc ํ์ผ ์์ฑ
ํฌ๋ฉงํ
๊ท์น ์ ์ ์ค์
{
"semi" : true ,
"singleQuote" : true ,
"quoteProps" : " as-needed" ,
"trailingComma" : " es5" ,
"tabWidth" : 2 ,
"useTabs" : false ,
"printWidth" : 80 ,
"endOfLine" : " lf" ,
"bracketSpacing" : true ,
"bracketSameLine" : false ,
"arrowParens" : " avoid" ,
"htmlWhitespaceSensitivity" : " css" ,
"jsxSingleQuote" : true ,
"proseWrap" : " preserve" ,
"embeddedLanguageFormatting" : " auto" ,
"singleAttributePerLine" : false
}
semi: ์ธ๋ฏธ์ฝ๋ก ์ฌ์ฉ (true: ;, false: ์์)
singleQuote: ์์๋ฐ์ดํ ์ฌ์ฉ (true: ', false: ")
quoteProps: ๊ฐ์ฒด ์์ฑ์ ๋ฐ์ดํ ์ฌ์ฉ ("as-needed": ํ์์๋ง, "consistent": ์ผ๊ด์ฑ, "preserve": ๋ณด์กด)
trailingComma: ํํ ์ผํ ์ฌ์ฉ ("none": ์์, "es5": ES5์์ ํ์ฉ๋๋ ๊ณณ, "all": ๋ชจ๋ ๊ณณ)
tabWidth: ํญ ๋๋น (๊ณต๋ฐฑ ์)
useTabs: ํญ ๋์ ๊ณต๋ฐฑ ์ฌ์ฉ (false: ๊ณต๋ฐฑ, true: ํญ)
printWidth: ํ ์ค ์ต๋ ๊ธธ์ด (๋ฌธ์ ์)
endOfLine: ์ค ๋ ๋ฌธ์ ("lf": \n, "crlf": \r\n, "cr": \r, "auto": ์๋)
bracketSpacing: ๊ฐ์ฒด ๋ฆฌํฐ๋ด ๊ดํธ ๋ด๋ถ ๊ณต๋ฐฑ (true: { foo }, false: {foo})
bracketSameLine: JSX ๋ซ๋ ๊ดํธ๋ฅผ ๊ฐ์ ์ค์ (false: ์ ์ค, true: ๊ฐ์ ์ค)
arrowParens: ํ์ดํ ํจ์ ๋งค๊ฐ๋ณ์ ๊ดํธ ("avoid": ๋จ์ผ ๋งค๊ฐ๋ณ์ ์ ์๋ต, "always": ํญ์ ์ฌ์ฉ)
htmlWhitespaceSensitivity: HTML ๊ณต๋ฐฑ ๋ฏผ๊ฐ๋ ("css": CSS display ์์ฑ ๊ธฐ์ค, "strict": ์๊ฒฉ, "ignore": ๋ฌด์)
jsxSingleQuote: JSX์์ ์์๋ฐ์ดํ ์ฌ์ฉ (true: ', false: ")
proseWrap: ๋งํฌ๋ค์ด ํ
์คํธ ์ค๋ฐ๊ฟ ("always": ํญ์, "never": ์ ๋, "preserve": ๋ณด์กด)
embeddedLanguageFormatting: ์๋ฒ ๋๋ ์ธ์ด ํฌ๋งทํ
("auto": ์๋, "off": ๋นํ์ฑํ)
singleAttributePerLine: JSX ์์ฑ์ ํ ์ค์ ํ๋์ฉ (false: ์ฌ๋ฌ ์์ฑ ํ์ฉ, true: ํ ์ค์ ํ๋)
4.4. /.prettierignore ํ์ผ ์์ฑ
ํฌ๋ฉงํ
์์ ์ ์ธํ ํ์ผ๋ค์ ๋ช
์ํจ.
# Dependencies
node_modules/
package-lock.json
yarn.lock
pnpm-lock.yaml
# Build outputs
.next/
out/
build/
dist/
# Environment files
.env
.env.local
.env.development.local
.env.test.local
.env.production.local
# Logs
* .log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
# Coverage directory used by tools like istanbul
coverage/
* .lcov
# nyc test coverage
.nyc_output
# Dependency directories
jspm_packages/
# Optional npm cache directory
.npm
# Optional eslint cache
.eslintcache
# Microbundle cache
.rpt2_cache/
.rts2_cache_cjs/
.rts2_cache_es/
.rts2_cache_umd/
# Optional REPL history
.node_repl_history
# Output of 'npm pack'
* .tgz
# Yarn Integrity file
.yarn-integrity
# parcel-bundler cache (https://parceljs.org/)
.cache
.parcel-cache
# Next.js build output
.next
# Nuxt.js build / generate output
.nuxt
# Gatsby files
.cache/
public
# Storybook build outputs
.out
.storybook-out
# Temporary folders
tmp/
temp/
# Editor directories and files
.vscode/
.idea/
* .swp
* .swo
* ~
# OS generated files
.DS_Store
.DS_Store?
._*
.Spotlight-V100
.Trashes
ehthumbs.db
Thumbs.db
# Generated files
* .min.js
* .min.css
* .bundle.js
* .bundle.css
# Documentation
CHANGELOG.md
LICENSE
# README.md
# Config files that should not be formatted
* .config.js
* .config.mjs
* .config.ts
4.5. ๋ช
๋ น์ด๋ก ํฌ๋ฉงํ
์ ํ๋ฒ์ ์คํํ๋๋ก ์คํฌ๋ฆฝํธ ์์ฑ(์ ํ)
package.json ์ Script ์ถ๊ฐ
"scripts" : {
"dev" : " next dev" ,
"build" : " next build" ,
"start" : " next start" ,
"lint" : " eslint" ,
"format" : " prettier --write ." ,
"format:check" : " prettier --check ." ,
"format:staged" : " prettier --write --ignore-unknown"
},
4.6. ์คํฌ๋ฆฝํธ ์คํ ์์
# ์ ์ฒด ํ๋ก์ ํธ ํฌ๋งทํ
npm run format
# ํฌ๋งทํ
์ฒดํฌ (๋ณ๊ฒฝ์ฌํญ ์์ด ํ์ธ๋ง)
npm run format:check
# ํน์ ํ์ผ๋ง ํฌ๋งทํ
npx prettier --write src/app/page.tsx
# ํน์ ๋๋ ํ ๋ฆฌ๋ง ํฌ๋งทํ
npx prettier --write src/components/
# ํฌ๋งทํ
๊ฒฐ๊ณผ ๋ฏธ๋ฆฌ๋ณด๊ธฐ (์ค์ ๋ณ๊ฒฝํ์ง ์์)
npx prettier --check src/app/page.tsx
์ฝ๋ ํ์ง ๊ฒ์ฌ ๋๊ตฌ
5.1. eslint.config.mjs ์ค์
rules: {
"@typescript-eslint/no-explicit-any" : "off" , // any ํ์
ํ์ฉ
}
์์ธํ ์ต์
์ ํฌํจํ ์์
// Node.js ๋ด์ฅ ๋ชจ๋์์ dirname ํจ์๋ฅผ ๊ฐ์ ธ์ด (ํ์ผ ๊ฒฝ๋ก์ ๋๋ ํ ๋ฆฌ๋ช
์ถ์ถ์ฉ)
import { dirname } from 'path' ;
// Node.js ๋ด์ฅ ๋ชจ๋์์ fileURLToPath ํจ์๋ฅผ ๊ฐ์ ธ์ด (URL์ ํ์ผ ๊ฒฝ๋ก๋ก ๋ณํ)
import { fileURLToPath } from 'url' ;
// ESLint์ FlatCompat ํด๋์ค๋ฅผ ๊ฐ์ ธ์ด (๊ธฐ์กด ์ค์ ํ์์ ์๋ก์ด flat config ํ์์ผ๋ก ๋ณํ)
import { FlatCompat } from '@eslint/eslintrc' ;
// ํ์ฌ ํ์ผ์ URL์ ํ์ผ ๊ฒฝ๋ก๋ก ๋ณํ (ES ๋ชจ๋์์ __filename ๋์ฒด)
const __filename = fileURLToPath ( import . meta. url ) ;
// ํ์ฌ ํ์ผ์ด ์์นํ ๋๋ ํ ๋ฆฌ ๊ฒฝ๋ก๋ฅผ ์ถ์ถ (ES ๋ชจ๋์์ __dirname ๋์ฒด)
const __dirname = dirname ( __filename ) ;
// FlatCompat ์ธ์คํด์ค๋ฅผ ์์ฑํ์ฌ ๊ธฐ์กด ESLint ์ค์ ์ ์๋ก์ด ํ์์ผ๋ก ๋ณํํ ์ ์๊ฒ ํจ
const compat = new FlatCompat ( {
baseDirectory : __dirname , // ๊ธฐ์ค ๋๋ ํ ๋ฆฌ๋ฅผ ํ์ฌ ํ๋ก์ ํธ ๋ฃจํธ๋ก ์ค์
} ) ;
// ESLint ์ค์ ๋ฐฐ์ด ์ ์ (flat config ํ์)
const eslintConfig = [
// Next.js์ ๊ธฐ๋ณธ ESLint ๊ท์น๋ค์ ํ์ฅ (์ฑ๋ฅ, ์ ๊ทผ์ฑ, TypeScript ๊ด๋ จ ๊ท์น ํฌํจ)
...compat . extends ( 'next/core-web-vitals' , 'next/typescript' ) ,
// ์ ์ญ ์ค์ : ESLint๊ฐ ๊ฒ์ฌํ์ง ์์ ํ์ผ/๋๋ ํ ๋ฆฌ ์ง์
{
ignores : [
'node_modules/**' , // npm ํจํค์ง๋ค์ด ์ค์น๋ ๋๋ ํ ๋ฆฌ (์ธ๋ถ ๋ผ์ด๋ธ๋ฌ๋ฆฌ)
'.next/**' , // Next.js ๋น๋ ๊ฒฐ๊ณผ๋ฌผ ๋๋ ํ ๋ฆฌ
'out/**' , // Next.js ์ ์ ๋ด๋ณด๋ด๊ธฐ ๊ฒฐ๊ณผ๋ฌผ ๋๋ ํ ๋ฆฌ
'build/**' , // ์ผ๋ฐ์ ์ธ ๋น๋ ๊ฒฐ๊ณผ๋ฌผ ๋๋ ํ ๋ฆฌ
'next-env.d.ts' , // Next.js TypeScript ํ๊ฒฝ ์ ์ ํ์ผ (์๋ ์์ฑ)
] ,
} ,
// ํ์ผ๋ณ ๊ท์น ์ค์ : ํน์ ํ์ผ ํ์ฅ์์ ์ ์ฉํ ๊ท์น๋ค
{
files : [ '**/*.{js,jsx,ts,tsx}' ] , // JavaScript, JSX, TypeScript, TSX ํ์ผ์ ์ ์ฉ
rules : {
// ===== Tailwind CSS ๊ด๋ จ ๊ท์น =====
'tailwindcss/classnames-order' : 'warn' , // Tailwind ํด๋์ค ์์๋ฅผ ์ผ๊ด๋๊ฒ ์ ๋ ฌ (๊ฒฝ๊ณ )
'tailwindcss/no-custom-classname' : 'warn' , // ์ ์๋์ง ์์ ์ปค์คํ
ํด๋์ค ์ฌ์ฉ ์ ๊ฒฝ๊ณ
'tailwindcss/no-contradicting-classname' : 'error' , // ์์ถฉํ๋ ํด๋์ค ์ฌ์ฉ ์ ์ค๋ฅ (์: hidden block)
// ===== React ๊ด๋ จ ๊ท์น =====
'react/jsx-key' : 'error' , // ๋ฐฐ์ด ๋ ๋๋ง ์ ๊ฐ ์์์ ๊ณ ์ ํ key prop ํ์ (์ค๋ฅ)
'react/no-unescaped-entities' : 'off' , // HTML ์ํฐํฐ(&, <, > ๋ฑ) ์ง์ ์ฌ์ฉ ํ์ฉ (๋นํ์ฑํ)
'react/display-name' : 'off' , // ํจ์ํ ์ปดํฌ๋ํธ์ displayName ์ค์ ํ์ ํด์ (๋นํ์ฑํ)
// ===== ์ผ๋ฐ์ ์ธ JavaScript/TypeScript ๊ท์น =====
'prefer-const' : 'error' , // ์ฌํ ๋น๋์ง ์๋ ๋ณ์๋ const ์ฌ์ฉ ๊ฐ์ (์ค๋ฅ)
'no-unused-vars' : 'off' , // ๊ธฐ๋ณธ unused variables ๊ท์น ๋นํ์ฑํ (TypeScript ๋ฒ์ ์ฌ์ฉ)
'@typescript-eslint/no-unused-vars' : [
// TypeScript์ฉ ์ฌ์ฉ๋์ง ์๋ ๋ณ์ ๊ฐ์ง ๊ท์น
'error' , // ์ค๋ฅ ๋ ๋ฒจ๋ก ์ค์
{ argsIgnorePattern : '^_' } , // _๋ก ์์ํ๋ ๋งค๊ฐ๋ณ์๋ ์ฌ์ฉํ์ง ์์๋ ํ์ฉ
] ,
'@typescript-eslint/no-explicit-any' : 'off' , // any ํ์
์ฌ์ฉ ํ์ฉ (ํ์
์์ ์ฑ ๊ท์น ๋นํ์ฑํ)
// ===== Import ๊ด๋ จ ๊ท์น =====
'import/order' : [
// import ๋ฌธ์ ์์์ ๊ทธ๋ฃนํ ๊ท์น
'error' , // ์ค๋ฅ ๋ ๋ฒจ๋ก ์ค์
{
groups : [
// import ๊ทธ๋ฃน ์์ ์ ์
'builtin' , // 1์์: Node.js ๋ด์ฅ ๋ชจ๋ (fs, path ๋ฑ)
'external' , // 2์์: npm ํจํค์ง (react, next ๋ฑ)
'internal' , // 3์์: ํ๋ก์ ํธ ๋ด๋ถ ๋ชจ๋ (@/components ๋ฑ)
'parent' , // 4์์: ์์ ๋๋ ํ ๋ฆฌ ๋ชจ๋ (../utils ๋ฑ)
'sibling' , // 5์์: ๊ฐ์ ๋๋ ํ ๋ฆฌ ๋ชจ๋ (./config ๋ฑ)
'index' , // 6์์: index ํ์ผ (./index ๋ฑ)
] ,
'newlines-between' : 'always' , // ๊ฐ ๊ทธ๋ฃน ์ฌ์ด์ ๋น ์ค ํ์
alphabetize : {
// ๊ทธ๋ฃน ๋ด์์ ์ํ๋ฒณ ์ ์ ๋ ฌ
order : 'asc' , // ์ค๋ฆ์ฐจ์ ์ ๋ ฌ (a-z)
caseInsensitive : true , // ๋์๋ฌธ์ ๊ตฌ๋ถ ์์ด ์ ๋ ฌ
} ,
} ,
] ,
} ,
} ,
] ;
// ESLint ์ค์ ์ ๊ธฐ๋ณธ ๋ด๋ณด๋ด๊ธฐ๋ก ์ค์
export default eslintConfig ;
5.2. Prettier ์ ESLint ํตํฉ ์ค์
ESLint ์ Prettier ์ถฉ๋ ํ์ง ์๋๋ก ์ค์
npm install --save-dev eslint-config-prettier
npm install --save-dev eslint-plugin-prettier
eslint.config.mjs์ prettier ์ค์ ์ถ๊ฐ
import { dirname } from 'path' ;
import { fileURLToPath } from 'url' ;
import { FlatCompat } from '@eslint/eslintrc' ;
// Prettier ํ๋ฌ๊ทธ์ธ ์ถ๊ฐ
import eslintPluginPrettier from 'eslint-plugin-prettier' ;
import eslintConfigPrettier from 'eslint-config-prettier' ;
const __filename = fileURLToPath ( import . meta. url ) ;
const __dirname = dirname ( __filename ) ;
const compat = new FlatCompat ( {
baseDirectory : __dirname ,
} ) ;
const eslintConfig = [
...compat . extends ( 'next/core-web-vitals' , 'next/typescript' , 'prettier' ) ,
{
plugins : {
prettier : eslintPluginPrettier , // Prettier ํ๋ฌ๊ทธ์ธ ์ถ๊ฐ
} ,
rules : {
...eslintConfigPrettier . rules , // Prettier์ ์ถฉ๋ํ๋ ESLint ๊ท์น ๋นํ์ฑํ
'prettier/prettier' : [ 'warn' , { endOfLine : 'auto' } ] , // Prettier ์คํ์ผ์ ๊ฐ์ ์ ์ฉ (์ค๋ฅ ๋ฐ์ ์ ESLint์์ ํ์)
'@typescript-eslint/no-unused-vars' : 'warn' , // ๊ธฐ์กด TypeScript ๊ท์น ์ ์ง
'@typescript-eslint/no-explicit-any' : 'off' , // any ํ์
์ฌ์ฉ ํ์ฉ
} ,
} ,
] ;
export default eslintConfig ;
/.vscode ํด๋ ์์ฑ
/.vscode/settings.json ํ์ผ ์์ฑ
{
"editor.defaultFormatter" : " esbenp.prettier-vscode" ,
"editor.formatOnSave" : true ,
"editor.codeActionsOnSave" : {
"source.fixAll.eslint" : " explicit"
}
}
{
// ===== ๊ธฐ๋ณธ ์๋ํฐ ์ค์ =====
"editor.formatOnSave" : true ,
"editor.formatOnPaste" : true ,
"editor.codeActionsOnSave" : {
"source.fixAll.eslint" : " explicit" ,
"source.organizeImports" : " explicit"
},
"editor.defaultFormatter" : " esbenp.prettier-vscode" ,
"editor.tabSize" : 2 ,
"editor.insertSpaces" : true ,
"editor.rulers" : [80 , 120 ],
"editor.wordWrap" : " on" ,
"editor.bracketPairColorization.enabled" : true ,
// ===== ํ์ผ ๊ด๋ จ ์ค์ =====
"files.autoSave" : " onFocusChange" ,
"files.trimTrailingWhitespace" : true ,
"files.insertFinalNewline" : true ,
"files.eol" : " \n " ,
"files.encoding" : " utf8" ,
// ===== ESLint ์ค์ =====
"eslint.enable" : true ,
"eslint.validate" : [
" javascript" ,
" javascriptreact" ,
" typescript" ,
" typescriptreact"
],
"eslint.format.enable" : true ,
// ===== Prettier ์ค์ =====
"prettier.enable" : true ,
"prettier.requireConfig" : true ,
// ===== ์ธ์ด๋ณ ํฌ๋งทํฐ ์ค์ =====
"[javascript]" : {
"editor.defaultFormatter" : " esbenp.prettier-vscode"
},
"[typescript]" : {
"editor.defaultFormatter" : " esbenp.prettier-vscode"
},
"[typescriptreact]" : {
"editor.defaultFormatter" : " esbenp.prettier-vscode"
}
}
editor.formatOnSave: ์ ์ฅ ์ ์๋ ํฌ๋งทํ
editor.codeActionsOnSave: ์ ์ฅ ์ ESLint ์๋ ์์ ๋ฐ import ์ ๋ฆฌ
files.autoSave: ํฌ์ปค์ค ๋ณ๊ฒฝ ์ ์๋ ์ ์ฅ
files.trimTrailingWhitespace: ์ค ๋ ๊ณต๋ฐฑ ์๋ ์ ๊ฑฐ
files.insertFinalNewline: ํ์ผ ๋์ ๋น ์ค ์๋ ์ฝ์
eslint.validate: ESLint๊ฐ ๊ฒ์ฌํ ํ์ผ ํ์ ์ง์
6.3. ํ์ฅ ํ๋ก๊ทธ๋จ ์ค์
/.vscode/extensions.json ํ์ฅ ํ๋ก๊ทธ๋จ ์ ์ ๋ด์ฉ ์์ฑ
{
"recommendations" : [
// ===== ํ์ ํ์ฅ ํ๋ก๊ทธ๋จ =====
" esbenp.prettier-vscode" ,
" dbaeumer.vscode-eslint" ,
" bradlc.vscode-tailwindcss" ,
" ms-vscode.vscode-typescript-next" ,
// ===== Next.js ๋ฐ React ๊ฐ๋ฐ =====
" formulahendry.auto-rename-tag" ,
" christian-kohler.path-intellisense" ,
" christian-kohler.npm-intellisense" ,
// ===== Git ๊ด๋ จ =====
" eamodio.gitlens" ,
" mhutchie.git-graph" ,
// ===== ๊ฐ๋ฐ ์์ฐ์ฑ =====
" redhat.vscode-yaml" ,
" yzhang.markdown-all-in-one" ,
// ===== ํ
๋ง ๋ฐ ์์ด์ฝ =====
" pkief.material-icon-theme" ,
" github.github-vscode-theme"
],
"unwantedRecommendations" : [" hookyqr.beautify" ]
}
6.4. VSCode Config ์ค์
์๋ํฐ์ ์ผ๊ด์ฑ์ ์ ์งํ๋ ๋ด์ฉ ์์ฑ ํ์ผ
/.editorconfig ํ์ผ
# ์ต์์ EditorConfig ํ์ผ
root = true
# ๋ชจ๋ ํ์ผ์ ๋ํ ๊ธฐ๋ณธ ์ค์
[* ]
charset = utf-8
end_of_line = lf
insert_final_newline = true
trim_trailing_whitespace = true
indent_style = space
indent_size = 2
# JavaScript/TypeScript ํ์ผ ์ค์
[*.{js,jsx,ts,tsx} ]
indent_style = space
indent_size = 2
end_of_line = lf
charset = utf-8
trim_trailing_whitespace = true
insert_final_newline = true
# JSON ํ์ผ ์ค์
[*.json ]
indent_style = space
indent_size = 2
end_of_line = lf
charset = utf-8
trim_trailing_whitespace = true
insert_final_newline = true
# CSS/SCSS ํ์ผ ์ค์
[*.{css,scss,sass,less} ]
indent_style = space
indent_size = 2
end_of_line = lf
charset = utf-8
trim_trailing_whitespace = true
insert_final_newline = true
# Markdown ํ์ผ ์ค์
[*.md ]
indent_style = space
indent_size = 2
end_of_line = lf
charset = utf-8
trim_trailing_whitespace = false
insert_final_newline = true
git remote add origin https://github.com/devgsheep/til_next_supabase.git
7.3. ํ์ฌ ๊น ์ฌ์ฉ์ ์ ๋ณด
git config user.name " ์์ด๋"
git config user.email " ์ด๋ฉ์ผ"