# 규칙
## 패키지 매니저
- **패키지 매니저** `bun`을 사용합니다.
## UI 컴포넌트 생성
- **ShadCN 컴포넌트를 우선적으로 활용합니다.**
- **ShadCN 컴포넌트 추가 명령어**:
- CLI 명령어 예시: `bunx shadcn@latest add accordion`
# Next.js Server Actions & API Routes 사용 지침
- 이 지침은 **Next.js** 프로젝트에서 **Server Actions**와 **API Routes**를 어떻게 적절히 사용할지에 대한 안내입니다.
## Next.js Server Actions
- **Next.js Server Actions**는 **간단한 데이터 작업** 또는 **기본 CRUD** 작업에 사용합니다. 이 기능은 컴포넌트 내에서 서버 작업을 직접 처리할 수 있어 추가적인 외부 API 호출이나 다단계 처리가 필요하지 않은 경우에 적합합니다.
- 복잡한 비즈니스 로직이나 외부 API 호출, 또는 다단계 처리가 필요하지 않은 경우에 Server Actions를 사용합니다.
- 예시
- 사용자별 데이터를 페이지에 로드.
- 간단한 폼 처리 (예: 새로운 항목 추가, 좋아요 버튼 클릭 처리).
## Next.js API Routes
- **Next.js API Routes**는 **복잡한 비즈니스 로직**이나 **외부 API 통신**, **세션 관리** 등의 작업에 사용합니다.
- 인중. 권한 관리, 또는 트랜잭션 같은 중요한 작업에서 API Routes를 사용하여 처리 흐름을 더 명확하게 관리할 수 있습니다.
- 외부 서비스와의 통합이나 다단계 프로세스가 필요한 경우 적합합니다.
- 예시:
- 결제 처리. 주문 관리, 외부 API 호출 등 복잡한 작업.
- 사용자 인증 및 권한 관리가 필요한 API 엔드포인트.
## 일반 규칙
- **Next.js** 프로젝트에서 간단한 데이터 처리는 **Server Actions**를 사용하여 성능 최적화와 코드 간결성을 유지합니다.
- 복잡한 로직, 확장성, 또는 외부 API 통합이 필요한 경우 **API Routes**를 사용합니다.
## Next.js 15 버전 방식 참고
아래 변경내용 참고해서 코드 작성해줘
Async Request APIs (Breaking change)
Previously synchronous Dynamic APIs that rely on runtime information are now asynchronous:
cookies
headers
draftMode
params in layout.js, page.js, route.js, default.js, opengraph-image, twitter-image, icon, and apple-icon.
searchParams in page.js
To ease the burden of migration, a codemod is available to automate the process and the APIs can temporarily be accessed synchronously.
cookies
Recommended Async Usage
import { cookies } from 'next/headers'
// Before
const cookieStore = cookies()
const token = cookieStore.get('token')
// After
const cookieStore = await cookies()
const token = cookieStore.get('token')
Temporary Synchronous Usage
app/page.tsx
TypeScript
TypeScript
import { cookies, type UnsafeUnwrappedCookies } from 'next/headers'
// Before
const cookieStore = cookies()
const token = cookieStore.get('token')
// After
const cookieStore = cookies() as unknown as UnsafeUnwrappedCookies
// will log a warning in dev
const token = cookieStore.get('token')
headers
Recommended Async Usage
import { headers } from 'next/headers'
// Before
const headersList = headers()
const userAgent = headersList.get('user-agent')
// After
const headersList = await headers()
const userAgent = headersList.get('user-agent')
Temporary Synchronous Usage
app/page.tsx
TypeScript
TypeScript
import { headers, type UnsafeUnwrappedHeaders } from 'next/headers'
// Before
const headersList = headers()
const userAgent = headersList.get('user-agent')
// After
const headersList = headers() as unknown as UnsafeUnwrappedHeaders
// will log a warning in dev
const userAgent = headersList.get('user-agent')
draftMode
Recommended Async Usage
import { draftMode } from 'next/headers'
// Before
const { isEnabled } = draftMode()
// After
const { isEnabled } = await draftMode()
Temporary Synchronous Usage
app/page.tsx
TypeScript
TypeScript
import { draftMode, type UnsafeUnwrappedDraftMode } from 'next/headers'
// Before
const { isEnabled } = draftMode()
// After
// will log a warning in dev
const { isEnabled } = draftMode() as unknown as UnsafeUnwrappedDraftMode
params & searchParams
Asynchronous Layout
app/layout.tsx
TypeScript
TypeScript
// Before
type Params = { slug: string }
export function generateMetadata({ params }: { params: Params }) {
const { slug } = params
}
export default async function Layout({
children,
params,
}: {
children: React.ReactNode
params: Params
}) {
const { slug } = params
}
// After
type Params = Promise<{ slug: string }>
export async function generateMetadata({ params }: { params: Params }) {
const { slug } = await params
}
export default async function Layout({
children,
params,
}: {
children: React.ReactNode
params: Params
}) {
const { slug } = await params
}
Synchronous Layout
app/layout.tsx
TypeScript
TypeScript
// Before
type Params = { slug: string }
export default function Layout({
children,
params,
}: {
children: React.ReactNode
params: Params
}) {
const { slug } = params
}
// After
import { use } from 'react'
type Params = Promise<{ slug: string }>
export default function Layout(props: {
children: React.ReactNode
params: Params
}) {
const params = use(props.params)
const slug = params.slug
}
Asynchronous Page
app/page.tsx
TypeScript
TypeScript
// Before
type Params = { slug: string }
type SearchParams = { [key: string]: string | string[] | undefined }
export function generateMetadata({
params,
searchParams,
}: {
params: Params
searchParams: SearchParams
}) {
const { slug } = params
const { query } = searchParams
}
export default async function Page({
params,
searchParams,
}: {
params: Params
searchParams: SearchParams
}) {
const { slug } = params
const { query } = searchParams
}
// After
type Params = Promise<{ slug: string }>
type SearchParams = Promise<{ [key: string]: string | string[] | undefined }>
export async function generateMetadata(props: {
params: Params
searchParams: SearchParams
}) {
const params = await props.params
const searchParams = await props.searchParams
const slug = params.slug
const query = searchParams.query
}
export default async function Page(props: {
params: Params
searchParams: SearchParams
}) {
const params = await props.params
const searchParams = await props.searchParams
const slug = params.slug
const query = searchParams.query
}
Synchronous Page
'use client'
// Before
type Params = { slug: string }
type SearchParams = { [key: string]: string | string[] | undefined }
export default function Page({
params,
searchParams,
}: {
params: Params
searchParams: SearchParams
}) {
const { slug } = params
const { query } = searchParams
}
// After
import { use } from 'react'
type Params = Promise<{ slug: string }>
type SearchParams = Promise<{ [key: string]: string | string[] | undefined }>
export default function Page(props: {
params: Params
searchParams: SearchParams
}) {
const params = use(props.params)
const searchParams = use(props.searchParams)
const slug = params.slug
const query = searchParams.query
}
// Before
export default function Page({ params, searchParams }) {
const { slug } = params
const { query } = searchParams
}
// After
import { use } from "react"
export default function Page(props) {
const params = use(props.params)
const searchParams = use(props.searchParams)
const slug = params.slug
const query = searchParams.query
}
Route Handlers
app/api/route.ts
// Before
type Params = { slug: string }
export async function GET(request: Request, segmentData: { params: Params }) {
const params = segmentData.params
const slug = params.slug
}
// After
type Params = Promise<{ slug: string }>
export async function GET(request: Request, segmentData: { params: Params }) {
const params = await segmentData.params
const slug = params.slug
}
app/api/route.js
// Before
export async function GET(request, segmentData) {
const params = segmentData.params
const slug = params.slug
}
// After
export async function GET(request, segmentData) {
const params = await segmentData.params
const slug = params.slug
}
runtime configuration (Breaking change)
The runtime segment configuration previously supported a value of experimental-edge in addition to edge. Both configurations refer to the same thing, and to simplify the options, we will now error if experimental-edge is used. To fix this, update your runtime configuration to edge. A codemod is available to automatically do this.
fetch requests
fetch requests are no longer cached by default.
To opt specific fetch requests into caching, you can pass the cache: 'force-cache' option.
app/layout.js
export default async function RootLayout() {
const a = await fetch('https://...') // Not Cached
const b = await fetch('https://...', { cache: 'force-cache' }) // Cached
// ...
}
To opt all fetch requests in a layout or page into caching, you can use the export const fetchCache = 'default-cache' segment config option. If individual fetch requests specify a cache option, that will be used instead.
app/layout.js
// Since this is the root layout, all fetch requests in the app
// that don't set their own cache option will be cached.
export const fetchCache = 'default-cache'
export default async function RootLayout() {
const a = await fetch('https://...') // Cached
const b = await fetch('https://...', { cache: 'no-store' }) // Not cached
// ...
}
Route Handlers
GET functions in Route Handlers are no longer cached by default. To opt GET methods into caching, you can use a route config option such as export const dynamic = 'force-static' in your Route Handler file.
app/api/route.js
export const dynamic = 'force-static'
export async function GET() {}
Client-side Router Cache
When navigating between pages via <Link> or useRouter, page segments are no longer reused from the client-side router cache. However, they are still reused during browser backward and forward navigation and for shared layouts.
To opt page segments into caching, you can use the staleTimes config option:
next.config.js
/** @type {import('next').NextConfig} */
const nextConfig = {
experimental: {
staleTimes: {
dynamic: 30,
static: 180,
},
},
}
module.exports = nextConfig
Layouts and loading states are still cached and reused on navigation.
next/font
The @next/font package has been removed in favor of the built-in next/font. A codemod is available to safely and automatically rename your imports.
app/layout.js
// Before
import { Inter } from '@next/font/google'
// After
import { Inter } from 'next/font/google'
bundlePagesRouterDependencies
experimental.bundlePagesExternals is now stable and renamed to bundlePagesRouterDependencies.
next.config.js
/** @type {import('next').NextConfig} */
const nextConfig = {
// Before
experimental: {
bundlePagesExternals: true,
},
// After
bundlePagesRouterDependencies: true,
}
module.exports = nextConfig
serverExternalPackages
experimental.serverComponentsExternalPackages is now stable and renamed to serverExternalPackages.
next.config.js
/** @type {import('next').NextConfig} */
const nextConfig = {
// Before
experimental: {
serverComponentsExternalPackages: ['package-name'],
},
// After
serverExternalPackages: ['package-name'],
}
module.exports = nextConfig
Speed Insights
Auto instrumentation for Speed Insights was removed in Next.js 15.
To continue using Speed Insights, follow the Vercel Speed Insights Quickstart guide.
NextRequest Geolocation
The geo and ip properties on NextRequest have been removed as these values are provided by your hosting provider. A codemod is available to automate this migration.
If you are using Vercel, you can alternatively use the geolocation and ipAddress functions from `@vercel/functions instead:
middleware.ts
import { geolocation } from '@vercel/functions'
import type { NextRequest } from 'next/server'
export function middleware(request: NextRequest) {
const { city } = geolocation(request)
// ...
}
middleware.ts
import { ipAddress } from '@vercel/functions'
import type { NextRequest } from 'next/server'
export function middleware(request: NextRequest) {
const ip = ipAddress(request)
// ...
}
bun
css
golang
javascript
mdx
next.js
react
shadcn/ui
+2 more
First Time Repository
오웬 블로그
TypeScript
Languages:
CSS: 1.9KB
JavaScript: 0.2KB
MDX: 0.9KB
TypeScript: 40.2KB
Created: 11/8/2024
Updated: 11/20/2024
All Repositories (1)
오웬 블로그