Redirecting
Next.jsμμ 리λλ μ μ μ²λ¦¬ν μ μλ λͺ κ°μ§ λ°©λ²μ΄ μμ΅λλ€. μ΄ νμ΄μ§μμλ μ¬μ© κ°λ₯ν κ° μ΅μ , μ¬μ© μ¬λ‘ λ° λλμ 리λλ μ μ κ΄λ¦¬νλ λ°©λ²μ μ΄ν΄λ΄ λλ€.
| API | λͺ©μ | μ΄λμμ(where) | μν μ½λ |
|---|---|---|---|
redirect | λ³κ²½ λλ μ΄λ²€νΈ λ°μ ν μ¬μ©μ 리λλ μ | μλ² μ»΄ν¬λνΈ, μλ² μ‘μ , λΌμ°νΈ νΈλ€λ¬ | 307(μμ) λλ 303(μλ² λμ) |
permanentRedirect | λ³κ²½ λλ μ΄λ²€νΈ λ°μ ν μ¬μ©μ 리λλ μ | μλ² μ»΄ν¬λνΈ, μλ² μ‘μ , λΌμ°νΈ νΈλ€λ¬ | 308 (Permanent) |
useRouter | ν΄λΌμ΄μΈνΈ μΈ‘ νμ μν | ν΄λΌμ΄μΈνΈ μ»΄ν¬λνΈμ μ΄λ²€νΈ νΈλ€λ¬ | N/A |
redirects in next.config.js | κ²½λ‘λ₯Ό κΈ°λ°μΌλ‘ λ€μ΄μ€λ μμ² λ¦¬λλ μ | next.config.js file | 307(μμ) λλ 308(μꡬ) |
NextResponse.redirect | 쑰건μ λ°λΌ μμ μμ² λ¦¬λλ μ νκΈ° | Middleware | Any |
1. redirect ν¨μ
리λλ μ
κΈ°λ₯μ μ¬μ©νλ©΄ μ¬μ©μλ₯Ό λ€λ₯Έ URLλ‘ λ¦¬λλ μ
ν μ μμ΅λλ€.
μλ² μ»΄ν¬λνΈ, λΌμ°νΈ νΈλ€λ¬, μλ² μ‘μ
μμ 리λλ μ
μ νΈμΆν μ μμ΅λλ€.
리λλ μ μ μ’ μ’ λ³κ²½μ΄λ μ΄λ²€νΈ μ΄νμ μ¬μ©λ©λλ€. μλ₯Ό λ€μ΄, κΈ μμ±κ³Ό κ°μ κ²½μ°μ λλ€:
1'use server'23import { redirect } from 'next/navigation'4import { revalidatePath } from 'next/cache'56export async function createPost(id: string) {7try {8// Call database9} catch (error) {10// Handle errors11}1213revalidatePath('/posts') // Update cached posts14redirect(`/post/${id}`) // Navigate to the new post page15}
μμλλ©΄ μ μ©ν©λλ€:
redirectλ κΈ°λ³Έμ μΌλ‘ 307(μμ 리λλ μ ) μν μ½λλ₯Ό λ°νν©λλ€.
- μλ² μ‘μ μμ μ¬μ©νλ©΄ 303(κΈ°ν μ°Έμ‘°)μ λ°ννλ©°,
- μ΄λ μΌλ°μ μΌλ‘ POST μμ²μ κ²°κ³Όλ‘ μ±κ³΅ νμ΄μ§λ‘ 리λλ μ νλ λ° μ¬μ©λ©λλ€.
redirectλ λ΄λΆμ μΌλ‘ μ€λ₯λ₯Ό λ°μμν€λ―λ‘ μλ/μ‘κΈ° λΈλ‘ μΈλΆμμ νΈμΆν΄μΌ ν©λλ€.redirectλ λ λλ§ νλ‘μΈμ€ μ€ ν΄λΌμ΄μΈνΈ μ»΄ν¬λνΈμμ νΈμΆν μ μμ§λ§ μ΄λ²€νΈ νΈλ€λ¬μμλ νΈμΆν μ μμ΅λλ€.
- λμ
useRouter νμ μ¬μ©ν μ μμ΅λλ€.redirectλ μ λ URLλ νμ©νλ©° μΈλΆ λ§ν¬λ‘ 리λλ μ νλ λ° μ¬μ©ν μ μμ΅λλ€.- λ λλ§ νλ‘μΈμ€ μ μ 리λλ μ νλ €λ©΄
next.config.jsλλλ―Έλ€μ¨μ΄λ₯Ό μ¬μ©νμΈμ.
μμΈν λ΄μ©μ redirect API μ°Έμ‘°λ₯Ό μ°Έμ‘°νμΈμ.
2. permanentRedirect ν¨μ
permanentRedirect ν¨μλ₯Ό μ¬μ©νλ©΄ μ¬μ©μλ₯Ό λ€λ₯Έ URLλ‘ μꡬμ μΌλ‘ 리λλ μ
ν μ μμ΅λλ€.
μλ² μ»΄ν¬λνΈ, λΌμ°νΈ νΈλ€λ¬, μλ² μ‘μ
μμ permanentRedirectμ νΈμΆν μ μμ΅λλ€.
permanentRedirectμ μ¬μ©μ μ΄λ¦μ λ³κ²½ν ν μ¬μ©μμ νλ‘ν URLμ μ
λ°μ΄νΈνλ λ±
μν°ν°μ μ μ URLμ λ³κ²½νλ λ³κ²½ λλ μ΄λ²€νΈ νμ μμ£Ό μ¬μ©λ©λλ€:
1'use server'23import { permanentRedirect } from 'next/navigation'4import { revalidateTag } from 'next/cache'56export async function updateUsername(username: string, formData: FormData) {7try {8// Call database9} catch (error) {10// Handle errors11}1213revalidateTag('username') // Update all references to the username14permanentRedirect(`/profile/${username}`) // Navigate to the new user profile15}
μμλλ©΄ μ μ©ν©λλ€:
permanentRedirectμ κΈ°λ³Έμ μΌλ‘ 308(μꡬ 리λλ μ ) μν μ½λλ₯Ό λ°νν©λλ€.permanentRedirectμ μ λ URLλ νμ©νλ©° μΈλΆ λ§ν¬λ‘ 리λλ μ νλ λ° μ¬μ©ν μ μμ΅λλ€.- λ λλ§ νλ‘μΈμ€ μ μ 리λλ μ νλ €λ©΄,
next.config.jsλλλ―Έλ€μ¨μ΄λ₯Ό μ¬μ©νμΈμ.
μμΈν λ΄μ©μ permanentRedirect API μ°Έμ‘°λ₯Ό μ°Έμ‘°νμΈμ.
3. useRouter() ν
ν΄λΌμ΄μΈνΈ μ»΄ν¬λνΈμ μ΄λ²€νΈ νΈλ€λ¬ λ΄λΆμμ 리λλ μ
ν΄μΌ νλ κ²½μ°, useRouter hookμ push λ©μλλ₯Ό μ¬μ©ν μ μμ΅λλ€. μλ₯Ό λ€μ΄
1'use client'23import { useRouter } from 'next/navigation'45export default function Page() {6const router = useRouter()78return (9<button type="button" onClick={() => router.push('/dashboard')}>10Dashboard11</button>12)13}
μμλλ©΄ μ μ©ν©λλ€:
μ¬μ©μλ₯Ό νλ‘κ·Έλλ° λ°©μμΌλ‘ νμν νμκ° μλ κ²½μ°,
<Link>μ»΄ν¬λνΈλ₯Ό μ¬μ©ν΄μΌ ν©λλ€.
μμΈν λ΄μ©μ useRouter API μ°Έμ‘°λ₯Ό μ°Έμ‘°νμΈμ.
4. next.config.js μμ redirects
next.config.js νμΌμ redirects μ΅μ
μ μ¬μ©νλ©΄ λ€μ΄μ€λ μμ² κ²½λ‘λ₯Ό λ€λ₯Έ λμ κ²½λ‘λ‘ λ¦¬λλ μ
ν μ μμ΅λλ€.
νμ΄μ§μ URL ꡬ쑰λ₯Ό λ³κ²½νκ±°λ 미리 μκ³ μλ 리λλ μ
λͺ©λ‘μ΄ μμ λ μ μ©ν©λλ€.
redirectsμ κ²½λ‘, ν€λ, μΏ ν€, 쿼리 λ§€μΉμ μ§μνλ―λ‘ μμ μμ²μ λ°λΌ μ μ°νκ² μ¬μ©μλ₯Ό 리λλ μ
ν μ μμ΅λλ€.
redirectsμ μ¬μ©νλ €λ©΄, next.config.js νμΌμ μ΅μ
μ μΆκ°νμΈμ:
1module.exports = {2async redirects() {3return [4// Basic redirect5{6source: '/about',7destination: '/',8permanent: true,9},10// Wildcard path matching11{12source: '/blog/:slug',13destination: '/news/:slug',14permanent: true,15},16]17},18}
μμΈν λ΄μ©μ redirects API μ°Έμ‘°λ₯Ό μ°Έμ‘°νμΈμ.
μμλλ©΄ μ μ©ν©λλ€:
redirectsμ μꡬ μ΅μ μΌλ‘ 307(μμ 리λλ μ ) λλ 308(μꡬ 리λλ μ ) μν μ½λλ₯Ό λ°νν μ μμ΅λλ€.redirectsμ νλ«νΌμ λ°λΌ μ νμ΄ μμ μ μμ΅λλ€.
- μλ₯Ό λ€μ΄, Vercelμμλ 리λλ μ μκ° 1,024κ°λ‘ μ νλμ΄ μμ΅λλ€.
- λ§μ μμ 리λλ μ (1000κ° μ΄μ)μ κ΄λ¦¬νλ €λ©΄,
λ―Έλ€μ¨μ΄λ₯Ό μ¬μ©νμ¬ μ¬μ©μ μ§μ μ루μ μ λ§λλ κ²μ κ³ λ €νμΈμ.- μμΈν λ΄μ©μ
λκ·λͺ¨ 리λλ μ κ΄λ¦¬νκΈ°μ μ μ°Έμ‘°νμΈμ.redirectsμ λ―Έλ€μ¨μ΄λ³΄λ€ λ¨Όμ μ€νλ©λλ€.
5. Middleware μμ NextResponse.redirect
λ―Έλ€μ¨μ΄λ₯Ό μ¬μ©νλ©΄ μμ²μ΄ μλ£λκΈ° μ μ μ½λλ₯Ό μ€νν μ μμ΅λλ€.
κ·Έλ° λ€μ λ€μ΄μ€λ μμ²μ λ°λΌ NextResponse.redirectλ₯Ό μ¬μ©νμ¬ λ€λ₯Έ URLλ‘ λ¦¬λλ μ
ν©λλ€.
μ΄λ 쑰건(μ: μΈμ¦, μΈμ
κ΄λ¦¬ λ±)μ λ°λΌ μ¬μ©μλ₯Ό 리λλ μ
νκ±°λ 리λλ μ
νμκ° λ§μ λ μ μ©ν©λλ€.
μλ₯Ό λ€μ΄, μ¬μ©μκ° μΈμ¦λμ§ μμ κ²½μ° /login νμ΄μ§λ‘ 리λλ μ
ν©λλ€:
1import { NextResponse, NextRequest } from 'next/server'2import { authenticate } from 'auth-provider'34export function middleware(request: NextRequest) {5const isAuthenticated = authenticate(request)67// If the user is authenticated, continue as normal8if (isAuthenticated) {9return NextResponse.next()10}1112// Redirect to login page if not authenticated13return NextResponse.redirect(new URL('/login', request.url))14}1516export const config = {17matcher: '/dashboard/:path*',18}
μμλλ©΄ μ μ©ν©λλ€:
λ―Έλ€μ¨μ΄λ
next.config.jsμredirectsν λ λλ§ μ μ μ€νλ©λλ€.
μμΈν λ΄μ©μ λ―Έλ€μ¨μ΄ μ€λͺ μλ₯Ό μ°Έμ‘°νμΈμ.
6. λκ·λͺ¨ 리λλ μ κ΄λ¦¬(κ³ κΈ)
λ§μ μμ 리λλ μ (1000κ° μ΄μ)μ κ΄λ¦¬νλ €λ©΄ λ―Έλ€μ¨μ΄λ₯Ό μ¬μ©νμ¬ μ¬μ©μ μ§μ μ루μ μ λ§λλ κ²μ κ³ λ €ν μ μμ΅λλ€. μ΄λ κ² νλ©΄, μ ν리μΌμ΄μ μ λ€μ λ°°ν¬ν νμ μμ΄ νλ‘κ·Έλλ° λ°©μμΌλ‘ 리λλ μ μ μ²λ¦¬ν μ μμ΅λλ€.
μ΄λ κ² νλ €λ©΄ λ€μ μ¬νμ κ³ λ €ν΄μΌ ν©λλ€:
- 리λλ μ λ§΅ μμ± λ° μ μ₯.
- λ°μ΄ν° μ‘°ν μ±λ₯ μ΅μ ν.
Next.js μμ : μλ κΆμ₯ μ¬νμ ꡬνμ λν μμ λ Bloom νν°κ° ν¬ν¨λ λ―Έλ€μ¨μ΄ μμ λ₯Ό μ°Έμ‘°νμΈμ.
6.1 리λλ μ λ§΅ μμ± λ° μ μ₯
리λλ μ λ§΅μ λ°μ΄ν°λ² μ΄μ€(μΌλ°μ μΌλ‘ ν€-κ° μ μ₯μ) λλ JSON νμΌμ μ μ₯ν μ μλ 리λλ μ λͺ©λ‘μ λλ€.
λ€μ λ°μ΄ν° ꡬ쑰λ₯Ό κ³ λ €νμΈμ:
1{2"/old": {3"destination": "/new",4"permanent": true5},6"/blog/post-old": {7"destination": "/blog/post-new",8"permanent": true9}10}
λ―Έλ€μ¨μ΄μμλ, Vercelμ Edge Config λλ Redisμ κ°μ λ°μ΄ν°λ² μ΄μ€μμ μ½κ³ λ€μ΄μ€λ μμ²μ λ°λΌ μ¬μ©μλ₯Ό 리λλ μ ν μ μμ΅λλ€:
1import { NextResponse, NextRequest } from 'next/server'2import { get } from '@vercel/edge-config'34type RedirectEntry = {5destination: string6permanent: boolean7}89export async function middleware(request: NextRequest) {10const pathname = request.nextUrl.pathname11const redirectData = await get(pathname)1213if (redirectData && typeof redirectData === 'string') {14const redirectEntry: RedirectEntry = JSON.parse(redirectData)15const statusCode = redirectEntry.permanent ? 308 : 30716return NextResponse.redirect(redirectEntry.destination, statusCode)17}1819// No redirect found, continue without redirecting20return NextResponse.next()21}
6.2 λ°μ΄ν° μ‘°ν μ±λ₯ μ΅μ ν
λ€μ΄μ€λ λͺ¨λ μμ²μ λν΄ λκ·λͺ¨ λ°μ΄ν° μ§ν©μ μ½λ κ²μ λλ¦¬κ³ λΉμ©μ΄ λ§μ΄ λ€ μ μμ΅λλ€. λ°μ΄ν° μ‘°ν μ±λ₯μ μ΅μ νν μ μλ λ κ°μ§ λ°©λ²μ΄ μμ΅λλ€:
- λΉ λ₯Έ μ½κΈ°μ μ΅μ νλ λ°μ΄ν°λ² μ΄μ€(μ: Vercel Edge Config λλ Redis)λ₯Ό μ¬μ©ν©λλ€.
- λΈλ£Έ νν°μ κ°μ λ°μ΄ν° μ‘°ν μ λ΅μ μ¬μ©νμ¬ λ ν° λ¦¬λλ μ νμΌμ΄λ λ°μ΄ν°λ² μ΄μ€λ₯Ό μ½κΈ° μ μ 리λλ μ μ΄ μ‘΄μ¬νλμ§ ν¨μ¨μ μΌλ‘ νμΈν©λλ€.
μμ μμλ₯Ό κ³ λ €νλ©΄ μμ±λ λΈλ£Έ νν° νμΌμ λ―Έλ€μ¨μ΄λ‘ κ°μ Έμ¨ λ€μ λ€μ΄μ€λ μμ² κ²½λ‘λͺ μ΄ λΈλ£Έ νν°μ μ‘΄μ¬νλμ§ νμΈν μ μμ΅λλ€.
μ‘΄μ¬νλ κ²½μ° μ€μ νμΌμ νμΈνκ³ μ¬μ©μλ₯Ό μ μ ν URLλ‘ λ¦¬λλ μ νλ λΌμ°νΈ νΈλ€λ¬λ‘ μμ²μ μ λ¬ν©λλ€. μ΄λ κ² νλ©΄ λͺ¨λ μμ μμ²μ μλλ₯Ό μ νμν¬ μ μλ λμ©λ 리λλ μ νμΌμ λ―Έλ€μ¨μ΄λ‘ κ°μ Έμ€λ κ²μ λ°©μ§ν μ μμ΅λλ€.
1import { NextResponse, NextRequest } from 'next/server'2import { ScalableBloomFilter } from 'bloom-filters'3import GeneratedBloomFilter from './redirects/bloom-filter.json'45type RedirectEntry = {6destination: string7permanent: boolean8}910// Initialize bloom filter from a generated JSON file11const bloomFilter = ScalableBloomFilter.fromJSON(GeneratedBloomFilter as any)1213export async function middleware(request: NextRequest) {14// Get the path for the incoming request15const pathname = request.nextUrl.pathname1617// Check if the path is in the bloom filter18if (bloomFilter.has(pathname)) {19// Forward the pathname to the Route Handler20const api = new URL(21`/api/redirects?pathname=${encodeURIComponent(request.nextUrl.pathname)}`,22request.nextUrl.origin,23)2425try {26// Fetch redirect data from the Route Handler27const redirectData = await fetch(api)2829if (redirectData.ok) {30const redirectEntry: RedirectEntry | undefined = await redirectData.json()3132if (redirectEntry) {33// Determine the status code34const statusCode = redirectEntry.permanent ? 308 : 3073536// Redirect to the destination37return NextResponse.redirect(redirectEntry.destination, statusCode)38}39}40} catch (error) {41console.error(error)42}43}4445// No redirect found, continue the request without redirecting46return NextResponse.next()47}
κ·Έλ° λ€μ Route Handlerμμ:
1import { NextRequest, NextResponse } from 'next/server'2import redirects from '@/app/redirects/redirects.json'34type RedirectEntry = {5destination: string6permanent: boolean7}89export function GET(request: NextRequest) {10const pathname = request.nextUrl.searchParams.get('pathname')11if (!pathname) {12return new Response('Bad Request', { status: 400 })13}1415// Get the redirect entry from the redirects.json file16const redirect = (redirects as Record<string, RedirectEntry>)[pathname]1718// Account for bloom filter false positives19if (!redirect) {20return new Response('No redirect', { status: 400 })21}2223// Return the redirect entry24return NextResponse.json(redirect)25}
μμλλ©΄ μ μ©ν©λλ€:
bloom-filtersλ₯Ό μμ±νλ €λ©΄ λΈλ£Έ νν°μ κ°μ λΌμ΄λΈλ¬λ¦¬λ₯Ό μ¬μ©ν μ μλλ€.- μ μμ μΈ μμ²μ λ°©μ§νκΈ° μν΄ λΌμ°νΈ νΈλ€λ¬μ λν μμ²μ μ ν¨μ±μ κ²μ¬ν΄μΌ ν©λλ€.