Project-hongsta(8)

강홍규(カンホンギュ)·2026년 1월 30일

Project

목록 보기
8/9
post-thumbnail

SNSプロジェクト - 한입(ひとくち)ログ 完全(かんぜん)セットアップガイド

🌟 プロジェクト概要(がいよう) (Project Overview)

한입(ひとくち)ログとは?

TwitterやThreads、Karriに類似(るいじ)したSNSサービスです。
様々(さまざま)な機能(きのう)を実装(じっそう)した本格的(ほんかくてき)なWebアプリケーションです。

트위터, 쓰레드, 커리어리와 유사한 SNS 서비스입니다.
다양한 기능을 구현한 본격적인 웹 애플리케이션입니다.

主要機能(しゅようきのう) (Core Features)

구현 예정 기능:

✅ 사용자 관리
- 회원가입 (Sign Up)
- 로그인 / 로그아웃 (Sign In / Out)
- 비밀번호 찾기 (Forget Password)
- 비밀번호 재설정 (Reset Password)
- 사용자 인증 및 인가

✅ 콘텐츠 기능
- 게시글 작성 / 수정 / 삭제
- 이미지 업로드
- 무한 스크롤 (Infinite Scroll)
- 좋아요 (Like)
- 무한 대댓글 (Nested Comments)

✅ UI/UX
- 다크 모드 (Dark Mode)
- 반응형 디자인
- 프로필 페이지

技術(ぎじゅつ)スタック (Tech Stack)

フロントエンド

React 18.3+ (UI 라이브러리)
├─ TypeScript (타입 안전성)
├─ Vite (빌드 도구)
├─ React Router 7 (라우팅)
├─ TanStack Query (서버 상태 관리)
├─ Tailwind CSS (스타일링)
└─ Shadcn/ui + Radix UI (UI 컴포넌트)

バックエンド & インフラ

Supabase (백엔드 서비스)
├─ 인증 (Authentication)
├─ 데이터베이스 (PostgreSQL)
├─ 스토리지 (파일 업로드)
└─ 실시간 구독 (Realtime)

Vercel (배포 플랫폼)
└─ 자동 배포, CDN, 도메인

重要(じゅうよう): 全(すべ)て無料(むりょう)プランを活用(かつよう) - すべて 무료 요금제만 활용 예정


🚀 プロジェクト初期(しょき)セットアップ

1단계: 依存性(いぞんせい)インストール

# 프로젝트 클론 또는 생성 후
npm install

# 모든 의존성 설치 완료!

何(なに)がインストールされるか?

주요 의존성:

react: UI 라이브러리
react-router: 페이지 라우팅
@tanstack/react-query: 서버 상태 관리
@supabase/supabase-js: Supabase SDK
tailwindcss: CSS 프레임워크
@radix-ui/*: UI 기본 컴포넌트
lucide-react: 아이콘 라이브러리

2단계: 開発(かいはつ)サーバー起動(きどう)

npm run dev

# 실행 결과:
# ➜  Local:   http://localhost:5173/
# ➜  Network: use --host to expose

ブラウザで確認(かくにん)

브라우저에서 http://localhost:5173 접속
→ 화면이 정상적으로 표시되면 성공!

⚙️ 設定(せってい)ファイル詳細(しょうさい)レビュー

package.json - 依存性(いぞんせい) 관리(かんり)

{
  "name": "hanib-log",
  "version": "1.0.0",
  "type": "module",
  "scripts": {
    "dev": "vite",           // 개발 서버 실행
    "build": "vite build",   // 프로덕션 빌드
    "preview": "vite preview" // 빌드 미리보기
  },
  "dependencies": {
    // === React 코어 ===
    "react": "^18.3.1",
    "react-dom": "^18.3.1",
    "react-router": "^7.1.3",
    
    // === 상태 관리 ===
    "@tanstack/react-query": "^5.62.7",
    "@tanstack/react-query-devtools": "^5.62.7",
    
    // === UI 컴포넌트 (Radix) ===
    "@radix-ui/react-avatar": "^1.1.2",
    "@radix-ui/react-dialog": "^1.1.4",
    "@radix-ui/react-dropdown-menu": "^2.1.4",
    "@radix-ui/react-label": "^2.1.1",
    "@radix-ui/react-slot": "^1.1.1",
    "@radix-ui/react-toast": "^1.2.4",
    
    // === 스타일링 ===
    "tailwindcss": "^3.4.17",
    "tailwind-merge": "^2.6.0",
    "clsx": "^2.1.1",
    
    // === 아이콘 ===
    "lucide-react": "^0.469.0",
    
    // === 백엔드 ===
    "@supabase/supabase-js": "^2.48.1"
  },
  "devDependencies": {
    // TypeScript 관련
    "typescript": "~5.6.2",
    "@types/react": "^18.3.18",
    "@types/react-dom": "^18.3.5",
    
    // ESLint (코드 품질)
    "eslint": "^9.17.0",
    
    // Prettier (코드 포맷팅)
    "prettier": "^3.4.2",
    "prettier-plugin-tailwindcss": "^0.6.9"
  }
}

主要(しゅよう)ライブラリ役割(やくわり)

ライブラリ役割(やくわり)使用(しよう)目的(もくてき)
@radix-ui/*UI 基礎(きそ)コンポーネントアクセシビリティ保証(ほしょう)
tailwindcssCSSフレームワーク迅速(じんそく)なスタイリング
@tanstack/react-queryサーバー状態(じょうたい)管理(かんり)캐싱, 리페칭 자동화(じどうか)

eslint.config.js - コード品質(ひんしつ)管理(かんり)

完全(かんぜん)な設定(せってい)ファイル

import js from "@eslint/js";
import globals from "globals";
import reactHooks from "eslint-plugin-react-hooks";
import reactRefresh from "eslint-plugin-react-refresh";
import tseslint from "typescript-eslint";

export default tseslint.config(
  // === 1. 무시할 폴더 설정 ===
  { 
    ignores: ["dist"]  // 빌드 결과물 폴더는 검사 안 함
  },
  
  // === 2. 메인 설정 ===
  {
    // TypeScript 추천 설정 상속
    extends: [
      js.configs.recommended,           // JavaScript 기본 규칙
      ...tseslint.configs.recommended   // TypeScript 기본 규칙
    ],
    
    // 검사 대상 파일
    files: ["**/*.{ts,tsx}"],  // 모든 .ts, .tsx 파일
    
    // 언어 옵션
    languageOptions: {
      ecmaVersion: 2020,       // ES2020 문법 사용
      globals: globals.browser, // 브라우저 전역 변수 허용
    },
    
    // 플러그인 등록
    plugins: {
      "react-hooks": reactHooks,       // React Hooks 규칙
      "react-refresh": reactRefresh,   // Fast Refresh 규칙
    },
    
    // 규칙 설정
    rules: {
      // React Hooks 규칙 (권장 설정)
      ...reactHooks.configs.recommended.rules,
      
      // React Refresh 규칙
      "react-refresh/only-export-components": [
        "warn",
        { allowConstantExport: true },
      ],
      
      /* === 프로젝트 커스텀 규칙 === */
      
      // 사용하지 않는 변수 에러 끄기
      // 이유: 개발 중 임시 변수가 많아서
      "@typescript-eslint/no-unused-vars": "off",
      
      // any 타입 사용 허용
      // 이유: 빠른 프로토타이핑을 위해
      "@typescript-eslint/no-explicit-any": "off",
    },
  }
);

規則(きそく)の意味(いみ)

✅ 켜진 규칙:
- react-hooks/rules-of-hooks: Hooks 규칙 위반 방지
- react-hooks/exhaustive-deps: useEffect 의존성 체크
- react-refresh/only-export-components: HMR 호환성

❌ 끈 규칙 (실습용):
- no-unused-vars: 사용 안 한 변수 허용
- no-explicit-any: any 타입 허용

실제 프로젝트에서는 이 규칙들을 켜는 것이 좋음!

tsconfig.app.json - TypeScript 設定(せってい)

Path Alias 設定(せってい)追加(ついか)

{
  "compilerOptions": {
    "target": "ES2020",
    "useDefineForClassFields": true,
    "lib": ["ES2020", "DOM", "DOM.Iterable"],
    "module": "ESNext",
    "skipLibCheck": true,

    /* === Bundler mode === */
    "moduleResolution": "bundler",
    "allowImportingTsExtensions": true,
    "isolatedModules": true,
    "moduleDetectionKind": "force",
    "noEmit": true,
    "jsx": "react-jsx",

    /* === Linting === */
    "strict": true,
    "noUnusedLocals": false,      // 사용 안 한 지역 변수 허용
    "noUnusedParameters": false,  // 사용 안 한 매개변수 허용
    "noFallthroughCasesInSwitch": true,
    "noUncheckedSideEffectImports": true,

    /* === 🎯 Path Alias 설정 (추가됨!) === */
    "baseUrl": ".",               // 기준 경로를 프로젝트 루트로
    "paths": {
      "@/*": ["./src/*"]          // @/ = src/ 폴더
    }
  },
  "include": ["src"]
}

Path Alias 使用例(しようれい)

// ❌ Before: 복잡한 상대 경로
import Button from "../../../components/ui/button";
import { fetchPosts } from "../../../../api/posts";

// ✅ After: 간결한 절대 경로
import Button from "@/components/ui/button";
import { fetchPosts } from "@/api/posts";

장점:
1. 경로가 짧고 명확
2. 파일 이동 시 import 경로 수정 불필요
3. 가독성 향상

tsconfig.node.json - Vite 設定用(せっていよう)

{
  "compilerOptions": {
    "target": "ES2022",
    "lib": ["ES2023"],
    "module": "ESNext",
    "skipLibCheck": true,

    /* === Bundler mode === */
    "moduleResolution": "bundler",
    "allowImportingTsExtensions": true,
    "isolatedModules": true,
    "moduleDetectionKind": "force",
    "noEmit": true,

    /* === Linting === */
    "strict": true,
    "noUnusedLocals": true,
    "noUnusedParameters": true,
    "noFallthroughCasesInSwitch": true,
    "noUncheckedSideEffectImports": true,

    /* === 🎯 Path Alias 설정 (추가됨!) === */
    "baseUrl": ".",
    "paths": {
      "@/*": ["./src/*"]
    }
  },
  "include": ["vite.config.ts"]
}

Note: Vite 설정(せってい) 파일(ふぁいる)을 위한(ための) TypeScript 설정(せってい)


.prettierrc.json - コードフォーマット

{
  "plugins": ["prettier-plugin-tailwindcss"]
}

Tailwind CSSクラス自動(じどう)整列(せいれつ)

// ❌ Before: 순서 뒤죽박죽
<div className="p-4 text-white bg-blue-500 flex items-center">

// ✅ After: Prettier 자동 정렬
<div className="flex items-center bg-blue-500 p-4 text-white">

정렬 순서:
1. 레이아웃 (flex, grid)
2. 위치 (absolute, relative)
3. 크기 (w-, h-)
4. 배경 (bg-)
5. 간격 (p-, m-)
6. 텍스트 (text-, font-)
...

components.json - Shadcn/ui 設定(せってい)

{
  "$schema": "https://ui.shadcn.com/schema.json",
  
  // === 스타일 테마 ===
  "style": "new-york",  // 'default' 또는 'new-york'
  
  // === React 설정 ===
  "rsc": false,         // React Server Components 사용 안 함
  "tsx": true,          // TypeScript + JSX 사용
  
  // === Tailwind 설정 ===
  "tailwind": {
    "config": "",                    // tailwind.config 경로 (자동 탐지)
    "css": "src/index.css",          // 글로벌 CSS 파일
    "baseColor": "neutral",          // 기본 색상 팔레트
    "cssVariables": true,            // CSS 변수 사용
    "prefix": ""                     // 클래스 접두사 없음
  },
  
  // === Path Alias 설정 ===
  "aliases": {
    "components": "@/components",    // 컴포넌트 폴더
    "utils": "@/lib/utils",          // 유틸 함수
    "ui": "@/components/ui",         // UI 컴포넌트
    "lib": "@/lib",                  // 라이브러리
    "hooks": "@/hooks"               // 커스텀 훅
  },
  
  // === 아이콘 라이브러리 ===
  "iconLibrary": "lucide"  // Lucide React 아이콘 사용
}

Shadcn/ui コンポーネント追加(ついか)

# 버튼 컴포넌트 추가
npx shadcn@latest add button

# 설치되는 것:
# src/components/ui/button.tsx
# → 프로젝트에 복사됨 (npm 패키지 아님!)

# 여러 개 동시 추가
npx shadcn@latest add button input label dialog

# 장점:
# 1. 소스 코드가 프로젝트에 포함됨
# 2. 자유롭게 커스터마이징 가능
# 3. TypeScript 완벽 지원

🔌 메인 진입점(しんにゅうてん) 설정(せってい)

main.tsx - アプリケーション起点(きてん)

import { createRoot } from "react-dom/client";
import "./index.css";  // Tailwind CSS 포함
import App from "./App.tsx";
import { BrowserRouter } from "react-router";
import { QueryClientProvider, QueryClient } from "@tanstack/react-query";
import { ReactQueryDevtools } from "@tanstack/react-query-devtools";

// === 1. TanStack Query 클라이언트 생성 ===
const queryClient = new QueryClient({
  defaultOptions: {
    queries: {
      // 기본 옵션 설정
      staleTime: 1000 * 60 * 5,      // 5분
      refetchOnWindowFocus: false,   // 윈도우 포커스 시 리페칭 안 함
    },
  },
});

// === 2. React 앱 렌더링 ===
createRoot(document.getElementById("root")!).render(
  // Router Provider (페이지 라우팅)
  <BrowserRouter>
    {/* TanStack Query Provider (서버 상태 관리) */}
    <QueryClientProvider client={queryClient}>
      {/* 개발 도구 (캐시 데이터 시각화) */}
      <ReactQueryDevtools />
      
      {/* 메인 앱 컴포넌트 */}
      <App />
    </QueryClientProvider>
  </BrowserRouter>
);

提供者(ていきょうしゃ)の順序(じゅんじょ)が重要(じゅうよう)!

올바른 순서:

<BrowserRouter>1. 라우팅 (가장 바깥)
  <QueryClientProvider>2. 서버 상태 관리
    <ReactQueryDevtools />3. 개발 도구
    <App />4. 메인 앱
  </QueryClientProvider>
</BrowserRouter>

이유:
- BrowserRouter는 전체 앱을 감싸야 함
- QueryClientProvider는 라우트 컴포넌트들이 사용
- ReactQueryDevtools는 쿼리 상태 모니터링

ReactQueryDevtools の役割(やくわり)

개발 도구 기능:

1. 캐시 데이터 시각화
   - 모든 쿼리 키와 데이터 표시
   - 상태 확인 (fresh, stale, inactive)

2. 실시간 모니터링
   - 리페칭 발생 시각화
   - 에러 추적

3. 수동 조작
   - 캐시 무효화
   - 쿼리 재실행
   - 캐시 데이터 편집

화면 하단 아이콘으로 토글 가능!

🛣️ 페이지(ぺーじ) 라우팅(らうてぃんぐ) 설정(せってい)

root-route.tsx - メインルート設定(せってい)

完全(かんぜん)な라우팅(らうてぃんぐ)構造(こうぞう)

import GlobalLayout from "@/components/layout/global-layout";
import ForgetPasswordPage from "@/pages/forget-password-page";
import IndexPage from "@/pages/index-page";
import PostDetailPage from "@/pages/post-detail-page";
import ProfileDetailPage from "@/pages/profile-detail-page";
import ResetPasswordPage from "@/pages/reset-password-page";
import SignInPage from "@/pages/sign-in-page";
import SignUpPage from "@/pages/sign-up-page";
import { Navigate, Route, Routes } from "react-router";

export default function RootRoute() {
  return (
    <Routes>
      {/* === 글로벌 레이아웃으로 모든 페이지 감싸기 === */}
      <Route element={<GlobalLayout />}>
        
        {/* === 인증 관련 페이지 === */}
        <Route 
          path="/sign-in" 
          element={<SignInPage />} 
        />
        <Route 
          path="/sign-up" 
          element={<SignUpPage />} 
        />
        <Route 
          path="/forget-password" 
          element={<ForgetPasswordPage />} 
        />
        <Route 
          path="/reset-password" 
          element={<ResetPasswordPage />} 
        />

        {/* === 메인 페이지 === */}
        <Route 
          path="/" 
          element={<IndexPage />} 
        />
        
        {/* === 동적 라우트 (URL 파라미터) === */}
        <Route 
          path="/post/:postId" 
          element={<PostDetailPage />} 
        />
        <Route 
          path="/profile/:userId" 
          element={<ProfileDetailPage />} 
        />

        {/* === 404 처리 (모든 잘못된 경로) === */}
        <Route 
          path="*" 
          element={<Navigate to={"/"} />} 
        />
      </Route>
    </Routes>
  );
}

ルート構造(こうぞう)分析(ぶんせき)

페이지 계층 구조:

GlobalLayout (공통 레이아웃)
├─ /sign-in           → 로그인 페이지
├─ /sign-up           → 회원가입 페이지
├─ /forget-password   → 비밀번호 찾기
├─ /reset-password    → 비밀번호 재설정
├─ /                  → 메인 피드 (IndexPage)
├─ /post/:postId      → 게시글 상세 (동적)
├─ /profile/:userId   → 프로필 페이지 (동적)
└─ /*                 → 404 → / 리다이렉트

動的(どうてき)ルートの使用(しよう)

// URL 파라미터 사용 예시:

// PostDetailPage.tsx
import { useParams } from "react-router";

export default function PostDetailPage() {
  const { postId } = useParams();
  // URL: /post/123 → postId = "123"
  
  return <div>게시글 {postId}</div>;
}

// ProfileDetailPage.tsx
export default function ProfileDetailPage() {
  const { userId } = useParams();
  // URL: /profile/abc → userId = "abc"
  
  return <div>사용자 {userId}의 프로필</div>;
}

Navigate コンポーネント (リダイレクト)

// 잘못된 경로 접근 시:
<Route path="*" element={<Navigate to={"/"} />} />

예시:
/unknown-page → 자동으로 / 이동
/post/abc/def → 자동으로 / 이동
/이상한경로   → 자동으로 / 이동

대신 사용할 수 있는 방법:
<Route path="*" element={<NotFoundPage />} />404 페이지 직접 표시

🎨 글로벌(ぐろーばる) 레이아웃(れいあうと) 컴포넌트

global-layout.tsx - 共通(きょうつう)レイアウト

完全(かんぜん)な実装(じっそう)

import { Link, Outlet } from "react-router";
import logo from "@/assets/logo.png";
import defaultAvatar from "@/assets/default-avatar.png";
import { SunIcon } from "lucide-react";

export default function GlobalLayout() {
  return (
    // === 전체 컨테이너: 최소 높이 100vh (뷰포트 전체) ===
    <div className="flex min-h-[100vh] flex-col">
      
      {/* ========== 헤더 (Header) ========== */}
      <header className="h-15 border-b">
        {/* 최대 너비 제한 + 중앙 정렬 컨테이너 */}
        <div className="m-auto flex h-full w-full max-w-175 justify-between px-4">
          
          {/* === 왼쪽: 로고 + 제목 === */}
          <Link to={"/"} className="flex items-center gap-2">
            <img
              className="h-5"
              src={logo}
              alt="한입 로그의 로고, 메세지 말풍선을 형상화한 모양이다"
            />
            <div className="font-bold">한입 로그</div>
          </Link>
          
          {/* === 오른쪽: 다크 모드 + 프로필 === */}
          <div className="flex items-center gap-5">
            {/* 다크 모드 토글 버튼 */}
            <div className="hover:bg-muted cursor-pointer rounded-full p-2">
              <SunIcon />
            </div>
            
            {/* 프로필 아바타 */}
            <img className="h-6" src={defaultAvatar} />
          </div>
        </div>
      </header>

      {/* ========== 메인 콘텐츠 영역 ========== */}
      <main className="m-auto w-full max-w-175 flex-1 border-x px-4 py-6">
        {/* 
          Outlet: 자식 라우트 렌더링 위치
          예: / → IndexPage
              /sign-in → SignInPage
        */}
        <Outlet />
      </main>

      {/* ========== 푸터 (Footer) ========== */}
      <footer className="text-muted-foreground border-t py-10 text-center">
        @winterlood
      </footer>
    </div>
  );
}

レイアウト構造(こうぞう)分析(ぶんせき)

화면 구조 (Flexbox 세로 배치):

┌──────────────────────────────┐
│        Header (고정 높이)      │ ← border-bottom
├──────────────────────────────┤
│                              │
│    Main (flex-1: 남은 공간)   │ ← Outlet (페이지 내용)
│                              │
│       최대 너비 제한           │
│       좌우 border             │
│                              │
├──────────────────────────────┤
│     Footer (자동 높이)         │ ← border-top
└──────────────────────────────┘

핵심 CSS:
- min-h-[100vh]: 최소 화면 전체 높이
- flex-col: 세로 배치
- flex-1: main이 남은 공간 모두 차지

ヘッダー詳細(しょうさい)

Header 구성:

┌────────────────────────────────────────┐
│  🖼️ Logo  한입 로그    ☀️  👤           │
│  ↑                    ↑   ↑           │
│  Link to "/"      Dark  Profile       │
│                   Mode                │
└────────────────────────────────────────┘

CSS 클래스 설명:
- h-15: 높이 60px (15 × 4px = 60px)
- border-b: 하단 테두리
- max-w-175: 최대 너비 700px (175 × 4px)
- justify-between: 양 끝 배치
- px-4: 좌우 패딩 16px

로고(ろご) + 제목(だいめい) 링크(りんく)

// Link 컴포넌트: 페이지 이동 (새로고침 없음)
<Link to={"/"} className="flex items-center gap-2">
  {/* 로고 이미지 */}
  <img
    className="h-5"  // 높이 20px
    src={logo}       // 이미지 경로
    alt="한입 로그의 로고, 메세지 말풍선을 형상화한 모양이다"
    // 웹 접근성: 스크린 리더를 위한 설명
  />
  
  {/* 제목 텍스트 */}
  <div className="font-bold">한입 로그</div>
</Link>

클릭 시:
/ (메인 페이지)로 이동 → IndexPage 렌더링

ダークモードボタン + アバター

<div className="flex items-center gap-5">
  {/* === 다크 모드 토글 === */}
  <div className="hover:bg-muted cursor-pointer rounded-full p-2">
    <SunIcon />
    {/* 
      Lucide React 아이콘
      - SunIcon: 라이트 모드
      - MoonIcon: 다크 모드 (추후 전환)
    */}
  </div>
  
  {/* === 프로필 아바타 === */}
  <img 
    className="h-6"         // 높이 24px
    src={defaultAvatar}     // 기본 아바타 이미지
    alt="사용자 프로필"
  />
</div>

CSS 설명:
- hover:bg-muted: 호버 시 배경색 변경
- cursor-pointer: 마우스 커서 포인터로
- rounded-full: 완전한 원형
- p-2: 패딩 8px (클릭 영역 확대)

メインコンテンツ領域(りょういき)

<main className="m-auto w-full max-w-175 flex-1 border-x px-4 py-6">
  <Outlet />
</main>

CSS 분석:
┌─────────────────────────────┐
│  m-auto: 가로 중앙 정렬      │
│  w-full: 너비 100%          │
│  max-w-175: 최대 700px      │
│  flex-1: 남은 공간 모두 차지  │
│  border-x: 좌우 테두리       │
│  px-4: 좌우 패딩 16px        │
│  py-6: 상하 패딩 24px        │
└─────────────────────────────┘

Outlet의 역할:
현재 라우트에 해당하는 페이지 컴포넌트 렌더링

예시:
URL: /<IndexPage />
URL: /sign-in<SignInPage />
URL: /post/123<PostDetailPage />

フッター

<footer className="text-muted-foreground border-t py-10 text-center">
  @winterlood
</footer>

CSS:
- text-muted-foreground: 회색 텍스트 (덜 강조)
- border-t: 상단 테두리
- py-10: 상하 패딩 40px
- text-center: 텍스트 중앙 정렬

📐 Tailwind CSS クラス完全(かんぜん)ガイド

使用(しよう)されたユーティリティクラス

/* === 레이아웃 === */
flex              /* display: flex */
flex-col          /* flex-direction: column (세로) */
flex-1            /* flex: 1 1 0% (남은 공간 차지) */
items-center      /* align-items: center (세로 중앙) */
justify-between   /* justify-content: space-between (양 끝) */

/* === 크기 === */
min-h-[100vh]     /* min-height: 100vh (최소 화면 높이) */
h-15              /* height: 60px (15 × 4px) */
h-6               /* height: 24px (6 × 4px) */
h-5               /* height: 20px (5 × 4px) */
w-full            /* width: 100% */
max-w-175         /* max-width: 700px (175 × 4px) */

/* === 간격 === */
gap-2             /* gap: 8px (2 × 4px) */
gap-5             /* gap: 20px (5 × 4px) */
px-4              /* padding-left, padding-right: 16px */
py-6              /* padding-top, padding-bottom: 24px */
py-10             /* padding-top, padding-bottom: 40px */
p-2               /* padding: 8px (전체) */
m-auto            /* margin: auto (중앙 정렬) */

/* === 테두리 === */
border            /* border: 1px solid */
border-b          /* border-bottom: 1px solid */
border-t          /* border-top: 1px solid */
border-x          /* border-left, border-right: 1px solid */

/* === 모양 === */
rounded-full      /* border-radius: 9999px (완전한 원) */

/* === 텍스트 === */
font-bold         /* font-weight: 700 */
text-center       /* text-align: center */
text-muted-foreground  /* color: hsl(var(--muted-foreground)) */

/* === 인터랙션 === */
cursor-pointer    /* cursor: pointer */
hover:bg-muted    /* hover 시 배경색 변경 */

🎯 핵심(かくしん) 요약(ようやく)

✅ 프로젝트 구조:

React + TypeScript + Vite
TanStack Query (서버 상태)
Supabase (백엔드)
Tailwind + Shadcn/ui (스타일)

✅ 설정 파일:

eslint.config.js: 코드 품질
tsconfig: TypeScript + Path Alias
prettier: Tailwind 자동 정렬
components.json: Shadcn/ui

✅ 라우팅:

React Router 7
중첩 라우트 (Outlet)
동적 파라미터 (:postId, :userId)
404 → / 리다이렉트

✅ 레이아웃:

GlobalLayout: 헤더 + 메인 + 푸터
Flexbox 세로 배치
flex-1로 메인이 남은 공간 차지
최대 너비 제한 (max-w-175)

profile
日本での就職を目指している26歳の韓国人開発者です。 アプリとweb開発、両方準備中で、日本語で技術概念を整理しながら日本語も一緒に勉強する予定です。 コツコツ続けるのが好きな開発者の成長記録を、一緒に見守っていただけると嬉しいです! 일본에서의 취업을 목표로 하고 있는 26살의 한국인 개발자입니다. 앱과 웹 개발, 둘 다 준비 중이며, 일본어로 기술 개념을 정리하면서 일본어도 함께 공부할 예정입니다. 꾸준히 계속하는 것을 좋아하는 개발자의 성장 기록을, 함께 지켜봐

0개의 댓글