diff --git a/CLAUDE.md b/CLAUDE.md index 955756a..cfa4a1c 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -7,13 +7,19 @@ This file provides guidance to Claude Code (claude.ai/code) when working with co - `pnpm dev` - Start development server on localhost:3000 - `pnpm build` - Build the application for production (configured for static export) - `pnpm start` - Start production server -- `pnpm biome:check` - Run Biome formatter and linter with auto-fix +- `pnpm biome:check` - Run Biome formatter and linter +- `pnpm biome:fix` - Run Biome formatter and linter with auto-fix - `pnpm biome:staged` - Run Biome on staged files only ## Code Quality This project uses Biome for formatting and linting. Always run `pnpm biome:check` before committing changes. The project has a pre-commit hook (lefthook) that automatically runs Biome on staged files. +**IMPORTANT**: +- Do NOT run `pnpm build` during development tasks unless specifically requested by the user. The build process is mainly for final deployment verification. +- When implementing new pages or components, use placeholders instead of actual content. Show what type of content should go in each position rather than writing fake content. +- Do NOT create UI structures arbitrarily. Always ask the user for specific requirements and approval before implementing any UI design or structure. + ## Git Workflow ### Branch Strategy @@ -48,19 +54,8 @@ Co-Authored-By: Claude - `fix: 모바일에서 네비게이션 메뉴 깨짐 수정` - `docs: CLAUDE.md에 Git 워크플로우 가이드 추가` -### Pull Request Process -1. Create feature branch from `main` -2. Implement changes with descriptive commits -3. Run `pnpm biome:check` and ensure build passes -4. Create PR with clear title and description -5. Link related issues if applicable -6. Merge after review (if working in team) or directly (if solo) - ### Pre-commit Hooks -The project uses lefthook to automatically run quality checks: -- Biome formatting and linting on staged files -- Prevents commits with linting errors -- Ensures consistent code style across the project +The project uses lefthook to automatically run quality checks ## Claude Code Workflow Instructions @@ -77,18 +72,22 @@ git checkout -b / ``` ### 2. Branch Naming Convention -- `feat/기능명` - New features (e.g., `feat/search-functionality`) -- `fix/버그명` - Bug fixes (e.g., `fix/mobile-nav-issue`) -- `docs/문서명` - Documentation updates (e.g., `docs/readme-update`) -- `config/설정명` - Configuration changes (e.g., `config/eslint-setup`) -- `refactor/리팩터명` - Code refactoring (e.g., `refactor/component-structure`) -- `chore/작업명` - Maintenance tasks (e.g., `chore/dependency-update`) +- `feat/` - New features (e.g., `feat/search-functionality`) +- `fix/` - Bug fixes (e.g., `fix/mobile-nav-issue`) +- `docs/` - Documentation updates (e.g., `docs/readme-update`) +- `config/` - Configuration changes (e.g., `config/eslint-setup`) +- `refactor/` - Code refactoring (e.g., `refactor/component-structure`) +- `chore/` - Maintenance tasks (e.g., `chore/dependency-update`) ### 3. After Completing Work ```bash -# Stage and commit changes -git add . -git commit -m "conventional commit message" +# Stage and commit changes LOGICALLY +# DO NOT commit everything at once - break into logical commits +git add [specific files for logical group 1] +git commit -m "conventional commit message for group 1" + +git add [specific files for logical group 2] +git commit -m "conventional commit message for group 2" # Push to remote git push -u origin @@ -99,7 +98,7 @@ gh pr create --title "PR Title" --body "$(cat <<'EOF' - Brief description of changes - Key implementation details -## Test plan +## Checklist - [ ] Checklist item 1 - [ ] Checklist item 2 @@ -108,18 +107,27 @@ EOF )" ``` +**IMPORTANT**: When the user asks to commit changes, NEVER create a single large commit. Always break changes into logical, separate commits such as: +- Documentation changes +- Configuration changes +- New component implementations +- Layout/styling updates +- Bug fixes +Each commit should represent one logical change or feature. + ### 4. PR Requirements - **Always create PRs**: Never commit directly to main - **Clear titles**: Use conventional commit format in PR titles - **Detailed descriptions**: Include Summary and Test plan sections - **Link issues**: Reference related GitHub issues when applicable -- **Quality checks**: Ensure CI passes before requesting review ### 5. Work Scope Guidelines - **One logical change per PR**: Keep PRs focused and reviewable - **Complete features**: Don't leave work in broken state -- **Test your changes**: Run `pnpm biome:check` and `pnpm build` before committing +- **Test your changes**: Run `pnpm biome:fix` before committing - **Document breaking changes**: Clearly explain any breaking changes +- **Use placeholders**: When implementing new pages/components, use placeholders like "[Page Title]", "[Description]", "[Content]" instead of actual content +- **No arbitrary UI**: Do NOT create UI structures without explicit user requirements and approval ### 6. After PR Creation - Wait for CI checks to pass @@ -142,30 +150,20 @@ This is a Next.js 15 blog application configured for static export with MDX supp - TypeScript - Biome for code formatting/linting + ### Directory Structure All client-side code is organized under `src/app/` following Next.js App Router conventions with a package-like structure: - `src/app/` - Next.js App Router root directory - - `_components/` - Global reusable React components (underscore prevents routing) - - `_hooks/` - Global custom React hooks (underscore prevents routing) - - `_fonts/` - Font configurations (underscore prevents routing) - - `_lib/` - Global utility functions and libraries (underscore prevents routing) - `about/` - About page route - - `_components/` - Components specific to about page only - - `_hooks/` - Hooks specific to about page only - - `page.tsx` - About page component - `blog/` - Blog routes - - `_components/` - Components specific to blog functionality - - `_hooks/` - Hooks specific to blog functionality - `[slug]/` - Dynamic blog post pages - - `page.tsx` - Blog index page - `layout.tsx` - Root layout component - `page.tsx` - Home page - `not-found.tsx` - 404 error page - `globals.css` - Global styles - `src/api/` - Server-side utilities (outside app directory) - - `posts.ts` - Blog post data fetching utilities - `src/contents/` - MDX blog posts (*.mdx files) **Naming Convention:** @@ -191,7 +189,7 @@ The app is configured for static export (`output: 'export'` in next.config.ts) a ### Styling - Uses Tailwind CSS with custom configuration -- shadcn/ui components with "new-york" style and stone base color +- shadcn/ui for UI components - CSS variables for theming (light/dark mode support) - Noto Sans KR font for Korean language support - Prose styling for blog content rendering @@ -205,6 +203,7 @@ This project uses shadcn/ui for consistent, high-quality UI components: - **Theming**: Full CSS variables support for light/dark themes - **Icons**: Uses Lucide React for icons - **Utilities**: `cn()` function in `src/app/_lib/utils.ts` for conditional styling +- **Color Palette**: Always use `stone` color palette for consistent design (e.g., `text-stone-900`, `border-stone-200`, `bg-stone-50`) ### Build Output diff --git a/components.json b/components.json index 7754090..b67333d 100644 --- a/components.json +++ b/components.json @@ -5,14 +5,14 @@ "tsx": true, "tailwind": { "config": "", - "css": "src/app/_styles/globals.css", + "css": "src/app/globals.css", "baseColor": "stone", "cssVariables": true, "prefix": "" }, "aliases": { "components": "@/app/_components", - "utils": "@/app/_lib/utils", + "utils": "@/app/_lib/cn", "ui": "@/app/_components/ui", "lib": "@/app/_lib", "hooks": "@/app/_hooks" diff --git a/lefthook.yml b/lefthook.yml index d016919..dce7bb7 100644 --- a/lefthook.yml +++ b/lefthook.yml @@ -2,4 +2,5 @@ pre-commit: commands: check: glob: "*.{js,ts,cjs,mjs,d.cts,d.mts,jsx,tsx,json,jsonc}" - run: npx @biomejs/biome check --no-errors-on-unmatched --files-ignore-unknown=true --colors=off {staged_files} \ No newline at end of file + run: npx @biomejs/biome check --write --no-errors-on-unmatched --files-ignore-unknown=true --colors=off {staged_files} + stage_fixed: true \ No newline at end of file diff --git a/package.json b/package.json index 28702e1..664df37 100644 --- a/package.json +++ b/package.json @@ -6,13 +6,16 @@ "dev": "next dev", "build": "next build", "start": "next start", - "biome:check": "biome check --write --verbose", - "biome:staged": "biome check --write --staged --verbose" + "biome:check": "biome check --verbose", + "biome:fix": "biome check --write --verbose", + "biome:staged": "biome check --write --staged" }, "dependencies": { "@mdx-js/loader": "^3.1.0", "@mdx-js/react": "^3.1.0", "@next/mdx": "^15.3.5", + "@radix-ui/react-separator": "^1.1.7", + "@radix-ui/react-slot": "^1.2.3", "@types/mdx": "^2.0.13", "class-variance-authority": "^0.7.1", "clsx": "^2.1.1", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 3ce4d73..bc57697 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -17,6 +17,12 @@ importers: '@next/mdx': specifier: ^15.3.5 version: 15.3.5(@mdx-js/loader@3.1.0(acorn@8.15.0))(@mdx-js/react@3.1.0(@types/react@19.1.8)(react@19.1.0)) + '@radix-ui/react-separator': + specifier: ^1.1.7 + version: 1.1.7(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + '@radix-ui/react-slot': + specifier: ^1.2.3 + version: 1.2.3(@types/react@19.1.8)(react@19.1.0) '@types/mdx': specifier: ^2.0.13 version: 2.0.13 @@ -363,6 +369,50 @@ packages: cpu: [x64] os: [win32] + '@radix-ui/react-compose-refs@1.1.2': + resolution: {integrity: sha512-z4eqJvfiNnFMHIIvXP3CY57y2WJs5g2v3X0zm9mEJkrkNv4rDxu+sg9Jh8EkXyeqBkB7SOcboo9dMVqhyrACIg==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + + '@radix-ui/react-primitive@2.1.3': + resolution: {integrity: sha512-m9gTwRkhy2lvCPe6QJp4d3G1TYEUHn/FzJUtq9MjH46an1wJU+GdoGC5VLof8RX8Ft/DlpshApkhswDLZzHIcQ==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-separator@1.1.7': + resolution: {integrity: sha512-0HEb8R9E8A+jZjvmFCy/J4xhbXy3TV+9XSnGJ3KvTtjlIUy/YQ/p6UYZvi7YbeoeXdyU9+Y3scizK6hkY37baA==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-slot@1.2.3': + resolution: {integrity: sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@shikijs/core@3.7.0': resolution: {integrity: sha512-yilc0S9HvTPyahHpcum8eonYrQtmGTU0lbtwxhA6jHv4Bm1cAdlPFRCJX4AHebkCm75aKTjjRAW+DezqD1b/cg==} @@ -1505,6 +1555,37 @@ snapshots: '@next/swc-win32-x64-msvc@15.3.5': optional: true + '@radix-ui/react-compose-refs@1.1.2(@types/react@19.1.8)(react@19.1.0)': + dependencies: + react: 19.1.0 + optionalDependencies: + '@types/react': 19.1.8 + + '@radix-ui/react-primitive@2.1.3(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)': + dependencies: + '@radix-ui/react-slot': 1.2.3(@types/react@19.1.8)(react@19.1.0) + react: 19.1.0 + react-dom: 19.1.0(react@19.1.0) + optionalDependencies: + '@types/react': 19.1.8 + '@types/react-dom': 19.1.6(@types/react@19.1.8) + + '@radix-ui/react-separator@1.1.7(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)': + dependencies: + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + react: 19.1.0 + react-dom: 19.1.0(react@19.1.0) + optionalDependencies: + '@types/react': 19.1.8 + '@types/react-dom': 19.1.6(@types/react@19.1.8) + + '@radix-ui/react-slot@1.2.3(@types/react@19.1.8)(react@19.1.0)': + dependencies: + '@radix-ui/react-compose-refs': 1.1.2(@types/react@19.1.8)(react@19.1.0) + react: 19.1.0 + optionalDependencies: + '@types/react': 19.1.8 + '@shikijs/core@3.7.0': dependencies: '@shikijs/types': 3.7.0 diff --git a/src/app/_components/Footer.tsx b/src/app/_components/Footer.tsx new file mode 100644 index 0000000..c475ab6 --- /dev/null +++ b/src/app/_components/Footer.tsx @@ -0,0 +1,56 @@ +import Link from 'next/link' + +export default function Footer() { + return ( +
+
+ {/* 3컬럼 구조 - 모바일에서는 1컬럼 */} +
+
+

+ [Site Name] +

+

[Site Description]

+
+ +
+

+ Contact +

+

+ + Email + +

+
+ +
+

Links

+
+ {/* TODO: RSS 추가 */} + + GitHub + +
+
+
+ +
+
+

+ © {new Date().getFullYear()} [Site Name]. All rights reserved. +

+
+
+
+
+ ) +} diff --git a/src/app/_components/Header.tsx b/src/app/_components/Header.tsx new file mode 100644 index 0000000..47a78ed --- /dev/null +++ b/src/app/_components/Header.tsx @@ -0,0 +1,31 @@ +import Link from 'next/link' + +export default function Header() { + return ( +
+ +
+ ) +} diff --git a/src/app/_lib/utils.ts b/src/app/_lib/cn.ts similarity index 100% rename from src/app/_lib/utils.ts rename to src/app/_lib/cn.ts diff --git a/src/app/about/page.tsx b/src/app/about/page.tsx index 8f697c4..8f2af87 100644 --- a/src/app/about/page.tsx +++ b/src/app/about/page.tsx @@ -1,3 +1,8 @@ export default function AboutPage() { - return
AboutPage
+ return ( +
+

[About Page Title]

+

[About Page Content]

+
+ ) } diff --git a/src/app/blog/[slug]/page.tsx b/src/app/blog/[slug]/page.tsx index c602753..626142b 100644 --- a/src/app/blog/[slug]/page.tsx +++ b/src/app/blog/[slug]/page.tsx @@ -27,12 +27,12 @@ export default async function PostPage({ ) return ( -
+
) } catch (_error) { - console.log(_error) + console.error(_error) notFound() } } diff --git a/src/app/globals.css b/src/app/globals.css index f1d8c73..e9f7055 100644 --- a/src/app/globals.css +++ b/src/app/globals.css @@ -1 +1,121 @@ @import "tailwindcss"; +@import "tw-animate-css"; +@plugin "@tailwindcss/typography"; + +@custom-variant dark (&:is(.dark *)); + +@theme inline { + --radius-sm: calc(var(--radius) - 4px); + --radius-md: calc(var(--radius) - 2px); + --radius-lg: var(--radius); + --radius-xl: calc(var(--radius) + 4px); + --color-background: var(--background); + --color-foreground: var(--foreground); + --color-card: var(--card); + --color-card-foreground: var(--card-foreground); + --color-popover: var(--popover); + --color-popover-foreground: var(--popover-foreground); + --color-primary: var(--primary); + --color-primary-foreground: var(--primary-foreground); + --color-secondary: var(--secondary); + --color-secondary-foreground: var(--secondary-foreground); + --color-muted: var(--muted); + --color-muted-foreground: var(--muted-foreground); + --color-accent: var(--accent); + --color-accent-foreground: var(--accent-foreground); + --color-destructive: var(--destructive); + --color-border: var(--border); + --color-input: var(--input); + --color-ring: var(--ring); + --color-chart-1: var(--chart-1); + --color-chart-2: var(--chart-2); + --color-chart-3: var(--chart-3); + --color-chart-4: var(--chart-4); + --color-chart-5: var(--chart-5); + --color-sidebar: var(--sidebar); + --color-sidebar-foreground: var(--sidebar-foreground); + --color-sidebar-primary: var(--sidebar-primary); + --color-sidebar-primary-foreground: var(--sidebar-primary-foreground); + --color-sidebar-accent: var(--sidebar-accent); + --color-sidebar-accent-foreground: var(--sidebar-accent-foreground); + --color-sidebar-border: var(--sidebar-border); + --color-sidebar-ring: var(--sidebar-ring); +} + +:root { + --radius: 0.625rem; + --background: oklch(1 0 0); + --foreground: oklch(0.147 0.004 49.25); + --card: oklch(1 0 0); + --card-foreground: oklch(0.147 0.004 49.25); + --popover: oklch(1 0 0); + --popover-foreground: oklch(0.147 0.004 49.25); + --primary: oklch(0.216 0.006 56.043); + --primary-foreground: oklch(0.985 0.001 106.423); + --secondary: oklch(0.97 0.001 106.424); + --secondary-foreground: oklch(0.216 0.006 56.043); + --muted: oklch(0.97 0.001 106.424); + --muted-foreground: oklch(0.553 0.013 58.071); + --accent: oklch(0.97 0.001 106.424); + --accent-foreground: oklch(0.216 0.006 56.043); + --destructive: oklch(0.577 0.245 27.325); + --border: oklch(0.923 0.003 48.717); + --input: oklch(0.923 0.003 48.717); + --ring: oklch(0.709 0.01 56.259); + --chart-1: oklch(0.646 0.222 41.116); + --chart-2: oklch(0.6 0.118 184.704); + --chart-3: oklch(0.398 0.07 227.392); + --chart-4: oklch(0.828 0.189 84.429); + --chart-5: oklch(0.769 0.188 70.08); + --sidebar: oklch(0.985 0.001 106.423); + --sidebar-foreground: oklch(0.147 0.004 49.25); + --sidebar-primary: oklch(0.216 0.006 56.043); + --sidebar-primary-foreground: oklch(0.985 0.001 106.423); + --sidebar-accent: oklch(0.97 0.001 106.424); + --sidebar-accent-foreground: oklch(0.216 0.006 56.043); + --sidebar-border: oklch(0.923 0.003 48.717); + --sidebar-ring: oklch(0.709 0.01 56.259); +} + +.dark { + --background: oklch(0.147 0.004 49.25); + --foreground: oklch(0.985 0.001 106.423); + --card: oklch(0.216 0.006 56.043); + --card-foreground: oklch(0.985 0.001 106.423); + --popover: oklch(0.216 0.006 56.043); + --popover-foreground: oklch(0.985 0.001 106.423); + --primary: oklch(0.923 0.003 48.717); + --primary-foreground: oklch(0.216 0.006 56.043); + --secondary: oklch(0.268 0.007 34.298); + --secondary-foreground: oklch(0.985 0.001 106.423); + --muted: oklch(0.268 0.007 34.298); + --muted-foreground: oklch(0.709 0.01 56.259); + --accent: oklch(0.268 0.007 34.298); + --accent-foreground: oklch(0.985 0.001 106.423); + --destructive: oklch(0.704 0.191 22.216); + --border: oklch(1 0 0 / 10%); + --input: oklch(1 0 0 / 15%); + --ring: oklch(0.553 0.013 58.071); + --chart-1: oklch(0.488 0.243 264.376); + --chart-2: oklch(0.696 0.17 162.48); + --chart-3: oklch(0.769 0.188 70.08); + --chart-4: oklch(0.627 0.265 303.9); + --chart-5: oklch(0.645 0.246 16.439); + --sidebar: oklch(0.216 0.006 56.043); + --sidebar-foreground: oklch(0.985 0.001 106.423); + --sidebar-primary: oklch(0.488 0.243 264.376); + --sidebar-primary-foreground: oklch(0.985 0.001 106.423); + --sidebar-accent: oklch(0.268 0.007 34.298); + --sidebar-accent-foreground: oklch(0.985 0.001 106.423); + --sidebar-border: oklch(1 0 0 / 10%); + --sidebar-ring: oklch(0.553 0.013 58.071); +} + +@layer base { + * { + @apply border-border outline-ring/50; + } + body { + @apply bg-background text-foreground; + } +} diff --git a/src/app/layout.tsx b/src/app/layout.tsx index d44f10f..bedc5dc 100644 --- a/src/app/layout.tsx +++ b/src/app/layout.tsx @@ -1,12 +1,14 @@ import type { Metadata } from 'next' +import Footer from './_components/Footer' +import Header from './_components/Header' import { notoSansKR } from './_fonts/notoSansKR' import './globals.css' export const metadata: Metadata = { - title: 'Blog', - description: 'Blog', + title: '[Site Title]', + description: '[Site Description]', } export default function RootLayout({ @@ -16,7 +18,13 @@ export default function RootLayout({ }>) { return ( - {children} + +
+
+
{children}
+
+
+ ) } diff --git a/src/app/page.tsx b/src/app/page.tsx index 8699fef..981bbef 100644 --- a/src/app/page.tsx +++ b/src/app/page.tsx @@ -1,11 +1,8 @@ -import Link from 'next/link' - export default function Home() { return ( -
- Hello World - About - Blog +
+

[Site Title]

+

[Site Description]

) }