๐ Next.js 15์ฅ: ํ๋ก์ ํธ ์ํคํ ์ฒ & ํด๋ ๊ตฌ์กฐ โ ์ค๋ฌด์์ ์ด์๋จ๋ ์ค๊ณ
๐ ๊ฐ์
์ค๋ฌด์์ ์ด์๋จ๋ Next.js ํด๋ ๊ตฌ์กฐ์ Feature ๊ธฐ๋ฐ ์ํคํ ์ฒ ์ค๊ณ ์์น์ ์ ๋ฆฌํฉ๋๋ค.
๐ ๋ชฉ์ฐจ
- ๐ ์ด ๋ฌธ์๋ฅผ ์ฝ๊ธฐ ์ ์
- ๐ค ์ ์์์ผ ํ๋๊ฐ
- ๐๏ธ ๋น์ ๋ก ๋จผ์ ์ดํดํ๊ธฐ
- ๐๏ธ ์ค๋ฌด ํด๋ ๊ตฌ์กฐ์ ์ ์ ๐ข
- ๐งฉ ๊ณ์ธต๋ณ ์ญํ ๊ณผ ๊ท์น โ ๋ฌด์์ด ์ด๋์ ์์ด์ผ ํ๋๊ฐ ๐ก
- ๐ DAL (Data Access Layer) ํจํด ๐ก
- ๐ฆ ์ปดํฌ๋ํธ ๋ถ๋ฅ: ui vs features ๐ก
- ๐ซ ์์กด์ฑ ๋ฐฉํฅ์ ๊ท์น โ ์ํ ์ฐธ์กฐ ๋ฐฉ์ง ๐ด
- ๐ฅ ์๋ฌ ํด๊ฒฐ ์นดํ๋ก๊ทธ
- ๐ ์ด๋ฒ์ ๋ฐฐ์ด ๋ด์ฉ ์ด์ ๋ฆฌ
- ๐ ๋ง๋ฌด๋ฆฌ ํด์ฆ
- ๐ ๋ ์์๋ณด๊ธฐ
๐ ์ด ๋ฌธ์๋ฅผ ์ฝ๊ธฐ ์ ์
โฑ๏ธ ์์ ์ฝ๊ธฐ ์๊ฐ: 15๋ถ (์ ์ฒด) / ํต์ฌ ํํธ๋ง: 8๋ถ
๐บ๏ธ ์ด ๋ฌธ์์ ๋ฐฐ๊ฒฝ ์ธ๊ณ๊ด: '์์๋ค ์ปค๋ฎค๋ํฐ'
- ์์ฒ (์ ์ ): "์ปดํฌ๋ํธ ํ์ผ์ด 50๊ฐ ๋์ด๊ฐ๋๊น ์ด๋์ ๋ญ ๋ฃ์ด์ผ ํ ์ง ๋ชจ๋ฅด๊ฒ ์ด์. ๊ณตํต ๋ฒํผ ์ปดํฌ๋ํธ๋ ๋ง๋ค์๋๋ฐ ์ด๋ ํด๋์ ๋ฃ์ด์ผ ํ๊ณ , API ํธ์ถํ๋ ํจ์๋ ์ด๋์ ์์ด์ผ ํ๊ณ , ์ ํธ ํจ์๋ ๋ ์ด๋์... utils/components/helpers ํด๋๊ฐ ๋ค์์ฌ์ ์ด๋์ ๋ญ ์ฐพ์์ผ ํ ์ง ๋จธ๋ฆฟ์์ด ํผ๋์ด์์."
- ์ํธ(๋ฆฌ๋): "์์ฒ ๋, ํด๋ ์ด๋ฆ์ด ์๋๋ผ '์ด ํ์ผ์ด ํ๋ ์ผ'๋ก ๋ถ๋ฅํด์ผ ํด์.
/lib์ '์๋ฒ์์๋ง ์คํ๋๋ ํต์ฌ ๋น์ฆ๋์ค ๋ก์ง',/components/ui๋ '๋๋ฉ์ธ์ ๋ชจ๋ฅด๋ ์์ UI',/components/features๋ '๋น์ฆ๋์ค๋ฅผ ์๋ UI'. ์ด ์ธ ๊ฐ์ง๋ง ์ง์ผ๋ 50๊ฐ๊ฐ 1000๊ฐ ๋ผ๋ ์ฐพ์ ์ ์์ด์."
๐บ๏ธ ์ด ๋ฌธ์์ ํ๋ฆ
์ค๋ฌด ํด๋ ๊ธฐ์ค ๊ตฌ์กฐ โ ๊ณ์ธต๋ณ ์ญํ ๊ท์น โ DAL ํจํด โ ์ปดํฌ๋ํธ ๋ถ๋ฅ โ ์์กด์ฑ ๋ฐฉํฅ
๐ฏ ์ด ๋ฌธ์๋ฅผ ๋ค ์ฝ์ผ๋ฉด ํ ์ ์๋ ๊ฒ
- ์ค๋ฌด์์ ๋ฐ๋ก ์ธ ์ ์๋ Next.js ํ๋ก์ ํธ ํด๋ ๊ตฌ์กฐ๋ฅผ ์ค๊ณํ ์ ์๋ค
- ์ ํ์ผ์ ๋ง๋ค ๋ ์ด๋ ํด๋์ ๋ฃ์ด์ผ ํ๋์ง ๋ฐ๋ก ํ๋จํ ์ ์๋ค
- DAL ํจํด์ผ๋ก ๋ฐ์ดํฐ ์ ๊ทผ ๋ก์ง์ ์์ ํ๊ฒ ๋ถ๋ฆฌํ ์ ์๋ค
๐ค ์ ์์์ผ ํ๋๊ฐ
ํ๋ก์ ํธ๊ฐ ์ปค์ง์๋ก ํด๋ ๊ตฌ์กฐ๋ ์ ์ ์ค์ํด์ ธ. ์๋ชป๋ ๊ตฌ์กฐ๋ก ์์ํ๋ฉด:
- ๊ฐ์ ํ์ผ์ ๋ ๋ช ์ด ๋์์ ์์ ํ๋ ์ถฉ๋์ด ๋น๋ฒํด์ง
- ์ด๋์ ๋ญ๊ฐ ์๋์ง ๋ชฐ๋ผ์ ๋งค๋ฒ ์ ์ฒด ๊ฒ์
- ํด๋ผ์ด์ธํธ์ ์๋ฒ ์ฝ๋๊ฐ ๋ค์์ฌ ๋ณด์ ์ทจ์ฝ์ ๋ฐ์ (์๋ฒ ๋น๋ฐํค๊ฐ ํด๋ผ์ด์ธํธ ๋ฒ๋ค์!)
- ์ํ ์ฐธ์กฐ๋ก ๋น๋ ์ค๋ฅ ๋ฐ์
"์ฒ์์ ๋์ถฉ ๋ง๋ค๊ณ ๋์ค์ ์ ๋ฆฌํ์"๋ ์๊ฐ์ ํ๋ก์ ํธ๊ฐ ํด์๋ก ๊ธฐ์ ๋ถ์ฑ๋ก ๋์์. ์ง๊ธ ์ฌ๋ฐ๋ฅธ ๊ตฌ์กฐ๋ก ์์ํ๋ ๊ฒ ๋์ค์ ๋ฆฌํฉํ ๋ง 3๋ฒ ํ๋ ๊ฒ๋ณด๋ค ๋น ๋ฅด๋ค.
๐๏ธ ๋น์ ๋ก ๋จผ์ ์ดํดํ๊ธฐ
๐ง 5์ด์๊ฒ ์ค๋ช ํ๋ค๋ฉด?
๋์๊ด์ ์๊ฐํด๋ด. ์ฑ ์ ๊ทธ๋ฅ ์๋ฌด ๋ฐ๋ ๊ฝ์ผ๋ฉด ๋์ค์ ์ฐพ์ ์ ์์ด.
์ธ๋ฌธํ, ๊ณผํ, ์์ค์ฒ๋ผ ๋ถ๋ฅ๊ฐ ์์ด์ผ ํด.ํ๋ก์ ํธ ํด๋๋ ๋ง์ฐฌ๊ฐ์ง์ผ. "์ด ์ฝ๋๊ฐ ํ๋ ์ผ"์ ๋ฐ๋ผ ๋ถ๋ฅํ๋ฉด ์ด๋์ ์์์ง ๋ฐ๋ก ์ ์ ์์ด.
lib์ ๋ฐฑ๊ณผ์ฌ์ ๊ตฌ์ญ(์๋ฒ ํต์ฌ ๋ก์ง),components/ui๋ ์ก์ง ๊ตฌ์ญ(์์ UI),app์ ์ด๋์ค(๋ผ์ฐํ ).
๐๏ธ ์ค๋ฌด ํด๋ ๊ตฌ์กฐ์ ์ ์ ๐ข
๐ฏ ์ด ์น์ ์ ์ฝ๊ณ ๋๋ฉด:
- 2026๋ Next.js ์ค๋ฌด์์ ๊ถ์ฅํ๋ ํด๋ ๊ตฌ์กฐ๋ฅผ ์ดํดํ๊ณ ๋ฐ๋ก ์ ์ฉํ ์ ์๋ค
my-app/
โโโ app/ # ๋ผ์ฐํ
์ ์ฉ โ ์ต๋ํ ์๊ฒ ์ ์ง
โ โโโ (auth)/ # ๋ผ์ฐํธ ๊ทธ๋ฃน (URL์ ์ํฅ ์์)
โ โ โโโ login/page.tsx
โ โ โโโ signup/page.tsx
โ โโโ (dashboard)/
โ โ โโโ layout.tsx # ๋์๋ณด๋ ๊ณตํต ๋ ์ด์์
โ โ โโโ page.tsx
โ โ โโโ settings/page.tsx
โ โโโ posts/
โ โ โโโ page.tsx
โ โ โโโ [id]/
โ โ โโโ page.tsx
โ โ โโโ opengraph-image.tsx
โ โโโ api/ # Route Handlers
โ โ โโโ posts/route.ts
โ โ โโโ webhooks/stripe/route.ts
โ โโโ layout.tsx # ๋ฃจํธ ๋ ์ด์์
โ โโโ globals.css
โ
โโโ components/
โ โโโ ui/ # ๋๋ฉ์ธ ๋ชจ๋ฅด๋ ์์ UI ์์ ๋จ์
โ โ โโโ Button.tsx
โ โ โโโ Input.tsx
โ โ โโโ Modal.tsx
โ โ โโโ Badge.tsx
โ โโโ features/ # ๋น์ฆ๋์ค ๋ก์ง์ ์๋ ๋๋ฉ์ธ ์ปดํฌ๋ํธ
โ โโโ auth/
โ โ โโโ LoginForm.tsx
โ โ โโโ SignupForm.tsx
โ โโโ posts/
โ โโโ PostCard.tsx
โ โโโ PostList.tsx
โ โโโ PostEditor.tsx
โ
โโโ lib/ # ์๋ฒ์์๋ง ์คํ๋๋ ํต์ฌ ๋น์ฆ๋์ค ๋ก์ง
โ โโโ dal.ts # Data Access Layer (DB ์ ๊ทผ + ์ธ์ฆ ๊ฒ์ฆ)
โ โโโ db.ts # DB ํด๋ผ์ด์ธํธ (Prisma, Drizzle ๋ฑ)
โ โโโ auth.ts # ์ธ์
์์ฑ/๊ฒ์ฆ ํจ์
โ โโโ actions/ # Server Actions ๋ชจ์
โ โ โโโ post.actions.ts
โ โ โโโ auth.actions.ts
โ โโโ schemas/ # Zod ์ ํจ์ฑ ๊ฒ์ฌ ์คํค๋ง
โ โโโ post.schema.ts
โ โโโ auth.schema.ts
โ
โโโ hooks/ # ํด๋ผ์ด์ธํธ ์ ์ฉ ์ปค์คํ
ํ
โ โโโ useAuth.ts
โ โโโ useInfiniteScroll.ts
โ
โโโ types/ # TypeScript ํ์
์ ์
โ โโโ post.types.ts
โ โโโ user.types.ts
โ
โโโ public/ # ์ ์ ํ์ผ (์ด๋ฏธ์ง, ํฐํธ ๋ฑ)
โ โโโ fonts/
โ
โโโ middleware.ts # Edge Middleware (ํ๋ก์ ํธ ๋ฃจํธ)
๐งฉ ๊ณ์ธต๋ณ ์ญํ ๊ณผ ๊ท์น โ ๋ฌด์์ด ์ด๋์ ์์ด์ผ ํ๋๊ฐ ๐ก
๐ฏ ์ด ์น์ ์ ์ฝ๊ณ ๋๋ฉด:
- ๊ฐ ํด๋์ ์ญํ ์ ํ ์ค๋ก ์ค๋ช ํ ์ ์๋ค
- ์ ํ์ผ์ ๋ง๋ค ๋ ์ด๋ ํด๋์ ๋ฃ์ด์ผ ํ ์ง ์ฆ์ ํ๋จํ ์ ์๋ค
ํ๋จ ๊ธฐ์ค: ์ด ์ฝ๋๊ฐ ์ด๋์ ์คํ๋๊ณ , ๋ฌด์์ ์๋๊ฐ?
| ํด๋ | ์คํ ํ๊ฒฝ | ๋น์ฆ๋์ค ์๋๊ฐ | ์ญํ |
|---|---|---|---|
app/ | ์๋ฒ (๊ธฐ๋ณธ) | O | ๋ผ์ฐํ , ๋ ์ด์์, ํ์ด์ง ์กฐํฉ |
lib/ | ์๋ฒ ์ ์ฉ | O | DB ์ ๊ทผ, ์ธ์ , ๋น์ฆ๋์ค ๋ก์ง |
components/features/ | ์๋ฒ/ํด๋ผ์ด์ธํธ | O | ๋๋ฉ์ธ ํฌํจ ๋ณตํฉ UI |
components/ui/ | ์๋ฒ/ํด๋ผ์ด์ธํธ | X | ์์ UI, ์ฌ์ฌ์ฉ ๊ฐ๋ฅํ ์์ ๋จ์ |
hooks/ | ํด๋ผ์ด์ธํธ๋ง | ๊ฒฝ์ฐ์ ๋ฐ๋ผ | React ์ํ, ์ด๋ฒคํธ ํธ๋ค๋ง |
types/ | ์์ (ํ์ ๋ง) | O | TypeScript ํ์ ์ ์ |
app/ ํด๋๋ ์๊ฒ ์ ์งํด์ผ ํด:
// โ ์์ฒ ์ด์ ๋น๋ํ page.tsx โ ๋น์ฆ๋์ค ๋ก์ง์ด page์ ์ง์
// app/posts/page.tsx
export default async function PostsPage() {
// ์ด๋ฐ ๋ก์ง์ด page.tsx์ ์์ผ๋ฉด ์ ๋ผ
const session = await cookies().get('session')
const user = await prisma.user.findUnique({ where: { sessionId: session } })
const posts = await prisma.post.findMany({ where: { authorId: user.id } })
// ...
}
// โ
์ํธ๊ฐ ๊ถ์ฅํ๋ ํจํด โ page๋ ์กฐํฉ๋ง, ๋ก์ง์ lib/dal.ts์
// app/posts/page.tsx
import { getMyPosts } from '@/lib/dal'
export default async function PostsPage() {
const posts = await getMyPosts() // ์ธ์ฆ + DB ์กฐํ + ๊ถํ ์ฒดํฌ๊ฐ dal์์ ์ฒ๋ฆฌ๋จ
return <PostList posts={posts} />
}๐ก ํ ์ค๋ก ๊ธฐ์ตํ๊ธฐ
app/ํด๋๋ "๋ ๊ณ ์กฐ๋ฆฝ ์ค๋ช ์"์ผ. ๋ธ๋ก์ ๋ง๋๋ ๊ณต์ฅ(lib,components)๊ณผ ์กฐ๋ฆฝํ๋ ์ฌ๋(app)์ ๋ฌ๋ผ์ผ ํด.
๐ DAL (Data Access Layer) ํจํด ๐ก
๐ฏ ์ด ์น์ ์ ์ฝ๊ณ ๋๋ฉด:
- DAL์ด ์ ํ์ํ์ง ์ค๋ช ํ ์ ์๋ค
lib/dal.ts์ ์ธ์ฆ + ๋ฐ์ดํฐ ์ ๊ทผ์ ๊ฒฐํฉํ ์์ ํ ํจ์๋ฅผ ์์ฑํ ์ ์๋ค
๐ ์ฉ์ด: DAL (Data Access Layer) โ ๋ฐ์ดํฐ๋ฒ ์ด์ค ์ ๊ทผ ๋ก์ง์ ํ ๊ณณ์ ๋ชจ์๋ ๊ณ์ธต์ด์ผ. ํ์ด์ง๋ ์ปดํฌ๋ํธ๊ฐ ์ง์ DB๋ฅผ ๊ฑด๋๋ฆฌ์ง ์๊ณ , ๋ฐ๋์ DAL์ ํตํ๋๋ก ํด.
DAL์ด ์์ผ๋ฉด ์ด๋ค ์ผ์ด ์๊ธฐ๋:
// โ DAL ์๋ ์ธ๊ณ โ ๋ชจ๋ page.tsx๊ฐ ์ง์ DB์ ์ ๊ทผ
// app/posts/[id]/page.tsx
const post = await prisma.post.findUnique({ where: { id } })
// ์ฌ๊ธฐ์ ์ค์๋ก ๊ถํ ์ฒดํฌ๋ฅผ ๋น ๋จ๋ฆฌ๋ฉด? ๋ค๋ฅธ ์ฌ๋ ๊ฒ์๊ธ์ ๋๊ตฌ๋ ๋ณผ ์ ์๋ ๋ณด์ ๊ตฌ๋ฉ!
// app/posts/page.tsx์์๋ ๋ prisma ์ง์ ํธ์ถ...
// app/dashboard/page.tsx์์๋ ๋...
// ๊ถํ ์ฒดํฌ ๋ก์ง์ด 20๊ตฐ๋ฐ์ ํฉ์ด์ ธ ์์ผ๋ฉด ํ๋๋ผ๋ ๋น ๋จ๋ฆด ์ ์์ด// โ
DAL ํจํด โ lib/dal.ts
import 'server-only' // ์ด ํ์ผ์ด ํด๋ผ์ด์ธํธ ๋ฒ๋ค์ ์ ๋ ํฌํจ๋์ง ์๋๋ก ๊ฐ์
import { cookies } from 'next/headers'
import { decrypt } from '@/lib/auth'
import { db } from '@/lib/db'
// ์ธ์
๊ฒ์ฆ ํจ์ โ DAL์ ๋ชจ๋ ํจ์๋ ์ด๊ฑธ ๋จผ์ ํธ์ถํด์ผ ํด
export async function verifySession() {
const cookieStore = await cookies()
const sessionCookie = cookieStore.get('session')?.value
if (!sessionCookie) return null
const session = await decrypt(sessionCookie)
if (!session?.userId) return null
return session
}
// ๊ฒ์๊ธ ๋ชฉ๋ก โ ์ธ์
๊ฒ์ฆ ํฌํจ
export async function getMyPosts() {
const session = await verifySession()
if (!session) throw new Error('๋ก๊ทธ์ธ์ด ํ์ํด์')
return db.post.findMany({
where: { authorId: session.userId },
orderBy: { createdAt: 'desc' },
})
}
// ๊ฒ์๊ธ ์์ธ โ ์์ฑ์ ๋๋ ๊ณต๊ฐ ๊ฒ์๊ธ๋ง ์ ๊ทผ ๊ฐ๋ฅ
export async function getPost(id: string) {
const session = await verifySession()
const post = await db.post.findUnique({ where: { id } })
if (!post) throw new Error('๊ฒ์๊ธ์ ์ฐพ์ ์ ์์ด์')
// ๋น๊ณต๊ฐ ๊ฒ์๊ธ์ ์์ฑ์๋ง ๋ณผ ์ ์์ด
if (post.isPrivate && post.authorId !== session?.userId) {
throw new Error('์ ๊ทผ ๊ถํ์ด ์์ด์')
}
// DTO ํจํด: ๋ฏผ๊ฐํ ํ๋(๋ด๋ถ ID, ๋น๋ฐ ๋ฑ) ์ ๊ฑฐํ๊ณ ๋ฐํ
return {
id: post.id,
title: post.title,
content: post.content,
createdAt: post.createdAt,
// authorInternalId: post.internalId โ ์ด๋ฐ ๋ฏผ๊ฐ ๋ฐ์ดํฐ๋ ์ ์ธ
}
}๐ก ํ ์ค๋ก ๊ธฐ์ตํ๊ธฐ
DAL์ "DB๋ก ๊ฐ๋ ์ ์ผํ ๋ฌธ"์ด์ผ. ๋ชจ๋ ๋ฐ์ดํฐ ์ ๊ทผ์ ์ด ๋ฌธ์ ํตํด์ผ ํด. ๋ฌธ ์์ ํญ์ ๊ฒฝ๋น์(์ธ์ ๊ฒ์ฆ)์ด ์ ์์ด.
๐ฆ ์ปดํฌ๋ํธ ๋ถ๋ฅ: ui vs features ๐ก
components/ui/ โ "๋๋ฉ์ธ์ ๋ชจ๋ฅด๋ ์์ UI"
// components/ui/Button.tsx
// โ
์์ ์ปค๋ฎค๋ํฐ๋ฅผ "์ ํ ๋ชจ๋ฅธ๋ค"๋ ๊ฒ ํต์ฌ
// variant, size, onClick๋ง ์๊ณ "๊ฒ์๊ธ", "์ ์ ", "๋๊ธ" ๊ฐ์ ๊ฐ๋
์ด ์ ํ ์์
interface ButtonProps {
variant?: 'primary' | 'secondary' | 'danger'
size?: 'sm' | 'md' | 'lg'
isLoading?: boolean
children: React.ReactNode
onClick?: () => void
}
export function Button({ variant = 'primary', size = 'md', ...props }: ButtonProps) {
return <button className={`btn btn-${variant} btn-${size}`} {...props} />
}components/features/ โ "๋น์ฆ๋์ค๋ฅผ ์๋ ๋๋ฉ์ธ ์ปดํฌ๋ํธ"
// components/features/posts/PostCard.tsx
// โ
"๊ฒ์๊ธ"์ด๋ผ๋ ๋๋ฉ์ธ์ ์๊ณ ์์
// PostType, ์ข์์, ๋๊ธ ์, ์์ฑ์ ๊ฐ์ ๋น์ฆ๋์ค ๊ฐ๋
์ด ํฌํจ๋จ
import { Button } from '@/components/ui/Button' // ui ์ปดํฌ๋ํธ ์ฌ์ฉ ๊ฐ๋ฅ
import type { Post } from '@/types/post.types'
interface PostCardProps {
post: Post
onLike: (postId: string) => void
}
export function PostCard({ post, onLike }: PostCardProps) {
return (
<div className="post-card">
<h3>{post.title}</h3>
<span>{post.author.name}</span>
<Button variant="secondary" onClick={() => onLike(post.id)}>
์ข์์ {post.likeCount}
</Button>
</div>
)
}๐ซ ์์กด์ฑ ๋ฐฉํฅ์ ๊ท์น โ ์ํ ์ฐธ์กฐ ๋ฐฉ์ง ๐ด
๐ฏ ์ด ์น์ ์ ์ฝ๊ณ ๋๋ฉด:
- ์ฌ๋ฐ๋ฅธ ์์กด์ฑ ๋ฐฉํฅ(
app โ components โ lib)์ ์ดํดํ๊ณ ์๋ฐ ์ผ์ด์ค๋ฅผ ํ์งํ ์ ์๋ค
์ฌ๋ฐ๋ฅธ ์์กด์ฑ ๋ฐฉํฅ (๋จ๋ฐฉํฅ, ํํฅ):
app/ โ (์ฌ์ฉ) components/, lib/, hooks/
components/features โ (์ฌ์ฉ) components/ui/, hooks/, types/
components/ui/ โ (์ฌ์ฉ) types/
lib/ โ (์ฌ์ฉ) types/, (์ธ๋ถ ํจํค์ง)
hooks/ โ (์ฌ์ฉ) types/, (React ํ
)
โ ์ ๋ ๊ธ์ง โ ์ญ๋ฐฉํฅ ์ฐธ์กฐ:
lib/ โ components/ (์๋ฒ ๋ก์ง์ด UI ์ปดํฌ๋ํธ๋ฅผ import? ๋ง์ด ์ ๋ผ)
components/ui/ โ components/features/ (ui๊ฐ ๋๋ฉ์ธ์ ์๊ฒ ๋๋ ์๊ฐ ์ฌ์ฌ์ฉ ๋ถ๊ฐ)
์ํ ์ฐธ์กฐ ์ค์ ์๋ฌ ์์:
// โ ์ํ ์ฐธ์กฐ ์ง์ฅ
// components/features/PostList.tsx
import { getMyPosts } from '@/lib/dal' // features๊ฐ lib์ import โ ๋์์ง ์์
// lib/dal.ts
import { PostCard } from '@/components/features/posts/PostCard' // โ ์ด๊ฑด ๊ธ์ง!
// lib(์๋ฒ ๋ก์ง)์ด UI ์ปดํฌ๋ํธ๋ฅผ importํ๋ฉด ํด๋ผ์ด์ธํธ/์๋ฒ ๊ฒฝ๊ณ๊ฐ ๋ฌด๋์งserver-only ํจํค์ง๋ก ์ค์ ๋ฐฉ์ง:
// lib/dal.ts ์ต์๋จ์ ์ถ๊ฐ
import 'server-only'
// ์ด ํ์ผ์ด ํด๋ผ์ด์ธํธ ์ปดํฌ๋ํธ์์ import๋๋ฉด ๋น๋ ์๋ฌ๋ฅผ ๋ด์ค
// ์ค์๋ก ์๋ฒ ๋น๋ฐ์ด ํด๋ผ์ด์ธํธ ๋ฒ๋ค์ ๋ค์ด๊ฐ๋ ๊ฑธ ๋ฐฉ์ง๐ฅ ์๋ฌ ํด๊ฒฐ ์นดํ๋ก๊ทธ
โ You're importing a component that needs... only works in a Client Component
์์ธ: lib/ ์์ ์๋ฒ ์ ์ฉ ํจ์๋ฅผ ํด๋ผ์ด์ธํธ ์ปดํฌ๋ํธ์์ import.
ํด๊ฒฐ์ฑ : ์๋ฒ ๋ก์ง์ ์๋ฒ ์ปดํฌ๋ํธ์์๋ง ํธ์ถํ๊ณ , ๊ฒฐ๊ณผ๋ฅผ props๋ก ํด๋ผ์ด์ธํธ ์ปดํฌ๋ํธ์ ์ ๋ฌํด.
โ Module not found โ tsconfig paths๊ฐ ์ ์๋
์์ธ: @/ ๊ฒฝ๋ก ๋ณ์นญ์ด ์ค์ ๋์ง ์์.
ํด๊ฒฐ์ฑ :
// tsconfig.json
{
"compilerOptions": {
"baseUrl": ".",
"paths": {
"@/*": ["./*"]
}
}
}๐ ์ด๋ฒ์ ๋ฐฐ์ด ๋ด์ฉ ์ด์ ๋ฆฌ
๐ ํ์ผ ๋ฐฐ์น ๊ฒฐ์ ๊ธฐ์ค
| ์ ํ์ผ์ ํน์ฑ | ์ด๋์ ๋ฃ์ด์ผ ํ๋๊ฐ |
|---|---|
| DB ์ง์ ์ ๊ทผ, ์ธ์ฆ ํ์ | lib/dal.ts (ํจ์ ์ถ๊ฐ) |
Server Action (use server) | lib/actions/*.ts |
| ๋๋ฉ์ธ ๋ชจ๋ฅด๋ ์์ ๋ฒํผ/์ ๋ ฅ/์นด๋ | components/ui/ |
| ๊ฒ์๊ธ/์ ์ ๊ฐ์ ๋๋ฉ์ธ ์๋ UI | components/features/[๋๋ฉ์ธ]/ |
useState, useEffect ํฌํจ ์ปค์คํ
ํ
| hooks/ |
| TypeScript ํ์ , ์ธํฐํ์ด์ค๋ง | types/ |
| ํ์ด์ง ์กฐํฉ, ๋ ์ด์์ | app/ |
โ ๏ธ ์ ๋ ํ์ง ๋ง ๊ฒ
| ์ํฉ | โ ๋์ ์ | โ ์ข์ ์ |
|---|---|---|
| page์์ DB ์ง์ ์ ๊ทผ | page.tsx์ Prisma ์ฝ๋ | lib/dal.ts ํจ์ ํธ์ถ |
| ui๊ฐ ๋๋ฉ์ธ ์๊ธฐ | Button์ด Post ํ์
์๊ธฐ | props๋ก ๋ฒ์ฉ interface ์ฌ์ฉ |
| ์ญ๋ฐฉํฅ ์ฐธ์กฐ | lib์ด components import | lib์ ์ธ๋ถ ํจํค์ง๋ง ์ฌ์ฉ |
๐ ๋ง๋ฌด๋ฆฌ ํด์ฆ
Q1. ๋ก๊ทธ์ธ ์ฌ์ฉ์์ ๊ฒ์๊ธ ๋ชฉ๋ก์ DB์์ ๊ฐ์ ธ์ค๋ ํจ์๋ฅผ ์ด๋์ ๋๋ ๊ฒ์ด ๋ง๋๊ฐ?
- A)
app/posts/page.tsx์์ inline์ผ๋ก - B)
components/features/posts/PostList.tsx์์ - C)
lib/dal.ts์์ export ํจ์๋ก - D)
hooks/usePosts.ts์์
โ ์ ๋ต: C
์ค๋ต ํด์ค:
- A โ page๋ ์๊ฒ ์ ์ง. ๋น์ฆ๋์ค ๋ก์ง ํฌํจ ๊ธ์ง
- B โ features ์ปดํฌ๋ํธ๋ UI ๋ด๋น. DB ์ง์ ์ ๊ทผ์ ์๋ฒ ์ ์ฉ ๊ณ์ธต์์
- D โ hooks๋ ํด๋ผ์ด์ธํธ ์ ์ฉ. DB ์ ๊ทผ ๋ถ๊ฐ
๐ ํต์ฌ ๊ธฐ์ต๋ฒ: "DB ๊ฑด๋๋ฆฌ๋ ๊ฑด ๋ฌด์กฐ๊ฑด
lib/."
Q2. components/ui/Button.tsx์์ importํ๋ฉด ์ ๋๋ ๊ฒ์?
- A) React, ReactNode
- B) CSS ๋ชจ๋ ํ์ผ
- C)
@/types/button.types.ts - D)
@/components/features/posts/PostCard.tsx
โ ์ ๋ต: D
ํด์ค:
ui์ปดํฌ๋ํธ๋ ๋๋ฉ์ธ ์ปดํฌ๋ํธ(features)๋ฅผ ์๋ฉด ์ ๋ผ. ์ฌ์ฌ์ฉ์ฑ์ด ์ฌ๋ผ์ง๊ณ ์์กด์ฑ์ด ์ํค๊ธฐ ์์ํด.๐ ํต์ฌ ๊ธฐ์ต๋ฒ: ui โ features ๋ฐฉํฅ import๋ ๊ธ์ง. ui๋ ํญ์ "์๋ ๋ฐฉํฅ"์ผ๋ก๋ง.
Q3. ์น๊ตฌ์๊ฒ ์ค๋ช ํ๋ค๋ฉด?
DAL ํจํด์ ์ฐ์ง ์์ผ๋ฉด ์ด๋ค ๋ฌธ์ ๊ฐ ์๊ธฐ๋์ง ๋น์ ๋ก ์ค๋ช ํด๋ด.
์์ ๋ต๋ณ:
"์ํ์ ๋น์ ํ๋ฉด ์ด๋. DAL ์์ด ๋ชจ๋ ์ฐฝ๊ตฌ(page)๊ฐ ์ง์ ๊ธ๊ณ (DB)์ ์ ๊ทผํ๋ฉด, ์ด๋ ์ฐฝ๊ตฌ๊ฐ ๊ถํ ์ฒดํฌ๋ฅผ ๋น ๋จ๋ ธ๋์ง ์ ์๊ฐ ์์ด. DAL์ ๊ธ๊ณ ์์ ๋ฑ ํ๋ ์๋ ๊ฒฝ๋น์ค์ด์ผ. ๊ฒฝ๋น์ค ํตํ์ง ์๊ณ ๋ ๊ธ๊ณ ์ ๋ชป ๋ค์ด๊ฐ. ๊ถํ ์ฒดํฌ๋ฅผ ๋น ๋จ๋ฆด ์๊ฐ ์๋ ๊ตฌ์กฐ์ผ."
๐ฃ ์์ฒ ์ด์ ํด๊ทผ ์ผ๊ธฐ
์ค๋์ ๋๋์ด ๊ฐ์ด๋์ ๋ง์ง๋ง ์ฅ์ธ '์ํคํ ์ฒ'๊น์ง ๋ง์ณค์ด! ์ฒ์์ ์ปดํฌ๋ํธ ํ๋ ํด๋์ ๋ฃ๋ ๊ฒ๋ ๊ณ ๋ฏผ์ด์๋๋ฐ, ์ด์ ๋ ์๋น์ค๊ฐ ์๋ฌด๋ฆฌ ์ปค์ ธ๋ ํ๋ค๋ฆฌ์ง ์์ ๋จ๋จํ ๋ผ๋๋ฅผ ์ค๊ณํ ์ ์๋ค๋ ์์ ๊ฐ์ด ์๊ฒผ์ด.
๐ก ์ค๋์ ๊ตํ: "ํด๋ ์ด๋ฆ์ด ์๋๋ผ ํ์ผ์ ์ญํ ๋ก ๋ถ๋ฅํ์. app์ ์๊ฒ, lib์ ํผํผํ๊ฒ! ๋จ๋จํ ์ํคํ ์ฒ๊ฐ ์๋น์ค๋ฅผ ์ด๋ฆฐ๋ค."
๋จ์ํ ๊ธฐ๋ฅ์ด ๋์๊ฐ๋ ๊ฑธ ๋์ด, ๋ณด์์ ์๊ฐํ๊ณ ๋๋ฃ๋ค์ด ์ฐพ๊ธฐ ์ฌ์ด ๊ตฌ์กฐ๋ฅผ ๋ง๋๋ ๊ฒ ์ง์ง '์๋์ด'์ ๊ธธ์ด๋ผ๋ ์ํธ ๋ฆฌ๋ ๋์ ๋ง์์ด ๊ฐ์ด์ ๊น๊ฒ ๋ฐํ์ด. ์์ผ๋ก ์ ํฌ ์๋น์ค๋ฅผ ์ธ์คํ๋ณด๋ค ๋ ๋ฉ์ง๊ฒ ๋ง๋ค๋ฉด ์ํธ ๋ฆฌ๋ ๋์ด ๋ง์๋ ์์ก ์ฌ์ฃผ์๊ฒ ์ง? ์ค๋์ ๊ฐ์ด๋ ์๋ ๊ธฐ๋ ์ผ๋ก ์ผ๊ธฐ์ฅ ๋ฎ๊ณ ์ผ์ฐ ํด๊ทผํด์ ๋ทํ๋ฆญ์ค๋ ๋ณด๋ฉด์ ํน ์ฌ์ด์ผ๊ฒ ๋ค! ๐ฃ