Skip to content

devgsheep/til_next_supabase

Folders and files

NameName
Last commit message
Last commit date

Latest commit

ย 

History

3 Commits
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 

Repository files navigation

Next.js ํ”„๋กœ์ ํŠธ ํ™˜๊ฒฝ ์„ค์ •

1. ํ”„๋กœ์ ํŠธ ์ƒ์„ฑ

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 ๋ฒ„์ „ ํ™•์ธ
"tailwindcss": "^4",

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;

3.3. global.css ์„ค์ •

  • 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;
}

4. Prettier ์„ค์ •

  • Prettier๋Š” ์ฝ”๋“œ ํฌ๋งทํ„ฐ๋กœ, ์ผ๊ด€๋œ ์ฝ”๋“œ ์Šคํƒ€์ผ์„ ์ž๋™์œผ๋กœ ์œ ์ง€ํ•จ.

4.1. ์„ค์น˜

  • 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
}

4.3. ์œ„ ๋‚ด์šฉ์˜ ์„ค๋ช…

  • 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. ESLint ์„ค์ •

  • ์ฝ”๋“œ ํ’ˆ์งˆ ๊ฒ€์‚ฌ ๋„๊ตฌ

5.1. eslint.config.mjs ์„ค์ •

  • rules ์ถ”๊ฐ€
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;

6. VSCode ์„ค์ • ๊ด€๋ฆฌ

  • /.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"
  }
}

6.2. ์ฐธ์กฐ ์„ค๋ช…

  • 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

7. Git ์„ค์ •

7.1. remote ์—ฐ๊ฒฐ

git remote add origin https://github.com/devgsheep/til_next_supabase.git

7.2. ํ˜„์žฌ ๊นƒ ์ƒํƒœ

git status

7.3. ํ˜„์žฌ ๊นƒ ์‚ฌ์šฉ์ž ์ •๋ณด

git config user.name "์•„์ด๋””"
git config user.email "์ด๋ฉ”์ผ"

About

Next.js + Supabase

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors