본문 바로가기
웹 프로그래밍/Next.js

[Next.js 공식문서 정리] Routing : 05. Redirecting

by 청량리 물냉면 2025. 3. 29.
반응형

5. Redirecting

Next.js에서는 리디렉션을 처리하는 다양한 방법이 존재한다. 이 페이지에서는 각 방법의 사용 가능한 옵션, 사용 사례 및 많은 수의 리디렉션을 관리하는 방법에 대해 설명하고 있다.

📌 리디렉션 방법 및 사용 위치

API 목적 사용 가능한 위치 상태 코드
redirect 데이터 변경(mutation or event, 예: 게시글 생성) 후 사용자 리디렉션 Server Components, Server Actions, Route Handlers 307(임시) 또는 303(Server Action)
permanentRedirect 데이터 변경(mutation or event, 예: 영구적인 URL 변경) 후 사용자 리디렉션 Server Components, Server Actions, Route Handlers 308(영구)
useRouter 클라이언트 사이드에서의 네비게이션 클라이언트 컴포넌트에서의 이벤트 핸들러 N/A
redirects in next.config.js 요청된 경로를 다른 경로로 리디렉션 next.config.js 파일 307(임시) 또는 308(영구)
NextResponse.redirect 특정 조건에 따라 리디렉션 미들웨어 모든 상태 코드 가능

 

🛠 redirect 함수

redirect 함수는 사용자를 특정 URL로 리디렉션하는 함수이다. Server Components, Route Handlers, Server Actions에서 호출 가능하다.

"use server";
import { redirect } from "next/navigation";
import { revalidatePath } from "next/cache";
export async function createPost(id: string) {
  try {
    // 데이터베이스 호출
  } catch (error) {
    // 오류 처리
  }
  revalidatePath("/posts"); // 캐시된 게시글 업데이트
  redirect(`/post/${id}`); // 새 게시글 페이지로 이동
}

🔎 Good to know

  • 기본적으로 redirect는 307(Temporary Redirect) 상태 코드를 반환한다. 서버 액션에서 사용될 때는 POST 요청 결과로 성공 페이지로 리디렉션하는 데 일반적으로 사용되는 303(다른 페이지 참조)을 반환한다.
  • redirect는 내부적으로 예외를 발생시키므로 try/catch 블록 밖에서 호출해야 한다.
  • 클라이언트 컴포넌트의 이벤트 핸들러에서는 redirect를 사용할 수 없으며, 대신 useRouter를 사용해야 한다.
  • redirect는 절대 URL을 허용하며, 외부 링크로도 리디렉션할 수 있다.
  • render process 이전에 리디렉션하려면 next.config.js 또는 미들웨어를 사용하면 된다.

 

🛠 permanentRedirect 함수

permanentRedirect 함수는 사용자를 영구적으로 다른 URL로 리디렉션할 때 사용된다.

영구 리디렉션 (permanentRedirect)은 엔티티의 표준 URL을 변경하는 변이 (mutation) 또는 이벤트 후에 자주 사용된다. 예를 들어, 사용자가 사용자 이름을 변경한 후 사용자의 프로필 URL을 업데이트하는 경우에 사용된다.

해석 및 설명
permanentRedirect는 보통 어떤 데이터(엔터티)의 URL이 변경된 후 사용됨.
예를 들어 사용자가 사용자명을 바꾸면 프로필 URL도 바뀔 수 있음.
그럴 때, 기존 URL에서 새로운 URL로 영구적으로 리디렉션(301 Redirect) 해주는 것

 

📌 예제로 쉽게 이해하기 (상황: 사용자 이름 변경)
1️⃣ 사용자의 기본 프로필 URL은 /profile/johndoe
2️⃣ 사용자가 johndoe → johnsmith로 이름 변경
3️⃣ 이제 새로운 프로필 URL은 /profile/johnsmith
4️⃣ 하지만 기존 URL (/profile/johndoe)로 접속하면 새로운 URL로 영구 이동해야 함
5️⃣ 이때 permanentRedirect('/profile/johnsmith') 사용

'use server'
​
import { permanentRedirect } from 'next/navigation'
import { revalidateTag } from 'next/cache'
​
export async function updateUsername(username: string, formData: FormData) {
  try {
    // 데이터베이스 호출
  } catch (error) {
    // 오류 처리
  }
​
  revalidateTag('username') // 모든 관련 캐시 업데이트
  permanentRedirect(`/profile/${username}`) // 새 프로필 페이지로 이동
}

🔎 Good to know

  • permanentRedirect은 기본적으로 308(영구 리디렉션) 상태 코드를 반환한다.
  • permanentRedirect은 절대 URL을 허용하며, 외부 링크로 리디렉션할 수 있다.
  • 렌더링 프로세스 전에 리디렉션하려면 next.config.js 또는 미들웨어를 사용해야 한다.


🛠 useRouter() 훅을 사용한 클라이언트 사이드 리디렉션


클라이언트 컴포넌트의 이벤트 핸들러에서 리디렉션하려면 useRouter 훅의 push 메서드를 사용해야 한다.

'use client'
​
import { useRouter } from 'next/navigation'
​
export default function Page() {
  const router = useRouter()
​
  return (
    <button type="button" onClick={() => router.push('/dashboard')}>
      대시보드 이동
    </button>
  )
}

🔎 Good to know

  • useRouter().push()를 사용하면 클라이언트 사이드 네비게이션이 수행된다.
  • 단순히 페이지 이동만 필요하다면 <Link> 컴포넌트를 사용하는 것이 더 적절하다.


🛠 next.config.js에서 redirects 설정

next.config.js 파일의 redirects 옵션을 사용하면 들어오는 요청 경로를 다른 대상 경로로 리디렉션할 수 있다. 이는 페이지의 URL 구조를 변경하거나 미리 알고 있는 리디렉션 목록이 있는 경우에 유용하다. redirectspath, header, cookie 및 query matching를 지원하여, 들어오는 요청을 기반으로 사용자를 리디렉션할 수 있는 유연성을 제공한다.
redirects를 사용하려면 next.config.js 파일에 옵션을 추가하면 된다.

import type { NextConfig } from 'next'
​
const nextConfig: NextConfig = {
  async redirects() {
    return [
      // 기본 리디렉션
      {
        source: '/about',
        destination: '/',
        permanent: true, // 308 (영구)
      },
      // 와일드카드 경로 매칭
      {
        source: '/blog/:slug',
        destination: '/news/:slug',
        permanent: true,
      },
    ]
  },
}
​
export default nextConfig

🔎 Good to know

  • permanent: true 설정 시 308(영구 리디렉션), false일 경우 307(임시 리디렉션) 상태 코드가 반환된다.
  • Vercel에서는 최대 1,024개의 리디렉션을 지원하며, 더 많은 리디렉션이 필요하면 미들웨어를 사용해야 한다.

 

🛠 미들웨어에서의 NextResponse.redirect


미들웨어에서는 특정 조건을 기반으로 리디렉션을 진행할 수 있다.
미들웨어를 사용하면 요청이 완료되기 전에 코드를 실행할 수 있다. 그런 다음 들어오는 요청을 기반으로 NextResponse.redirect를 사용하여 다른 URL로 리디렉션할 수 있다.
이는 사용자를 조건(예: 인증, 세션 관리 등)에 따라 리디렉션하거나 많은 리디렉션을 수행해야 하는 경우 유용하다. (예를 들어, 로그인되지 않은 사용자를 로그인 페이지로 보내는 경우)

import { NextResponse, NextRequest } from 'next/server'
import { authenticate } from 'auth-provider'
​
export function middleware(request: NextRequest) {
  const isAuthenticated = authenticate(request)
​
  if (isAuthenticated) {
    return NextResponse.next() // 정상 요청 진행
  }
​
  return NextResponse.redirect(new URL('/login', request.url)) // 로그인 페이지로 리디렉션
}
​
export const config = {
  matcher: '/dashboard/:path*', // 특정 경로에 대해 실행
}

🔎 Good to know

  • next.config.js의 리디렉션보다 미들웨어가 후순위로 실행된다.

 

📌 대량의 리디렉션 관리 방법 (1000+ 개 이상)

리디렉션이 많아지면 성능 문제가 발생할 수 있다. 이를 해결하기 위해 미들웨어 + 데이터베이스(또는 JSON 파일) 방식으로 리디렉션을 처리할 수 있다.

1️⃣ redirect map (JSON 또는 데이터베이스) 생성 및 저장


redirect map은 데이터베이스(보통 키-값 저장소) 또는 JSON 파일에서 저장할 수 있는 리디렉션 목록

{
  "/old": { "destination": "/new", "permanent": true },
  "/blog/post-old": { "destination": "/blog/post-new", "permanent": true }
}

미들웨어에서 Vercel의 Edge Config 또는 Redis와 같은 데이터베이스에서 읽을 수 있으며, 들어오는 요청에 따라 사용자를 리디렉션할 수 있다.


2️⃣ 데이터 조회 성능 최적화 (Optimizing data lookup performance)

모든 들어오는 요청에 대해 큰 데이터 세트를 읽는 것은 느리고 비용이 많이 들 수 있다. 데이터 조회 성능을 최적화 할 수있는 두 가지 방법이 존재한다.

  1. 빠른 읽기 기능이 최적화된 데이터베이스(예: Vercel Edge Config 또는 Redis)를 사용
  2. Bloom 필터와 같은 데이터 조회 전략을 사용하여, 대규모 리다이렉트 파일 또는 데이터베이스를 읽기 전에 리다이렉트가 있는지 효율적으로 확인

이전 예제를 고려하면, 생성된 Bloom 필터 파일을 미들웨어에 가져와서 들어오는 요청 경로 이름이 Bloom 필터에 있는지 확인할 수 있다.
그렇지 않은 경우, 요청을 라우트 핸들러에 전달하여 실제 파일을 확인하고 사용자를 해당 URL로 리디렉션한다.
이렇게하면 대규모 리디렉션 파일을 미들웨어로 가져 오는 것을 피하면 모든 들어오는 요청이 느려질 수 있습니다.

설명 및 해석
Bloom Filter를 이용해 리디렉션을 최적화하는 방법을 설명 중.
미들웨어(Middleware) 에서 모든 리디렉트 정보를 직접 로드하는 대신, Bloom Filter를 사용해서 빠르게 존재 여부를 확인한 후, 실제로 리디렉션이 필요하면 Route Handler에서 처리하도록 분리하는 방식을 설명하고 있다.

 

🛠 예제로 쉽게 이해하기
1️⃣ 기본적인 문제: 대량의 리디렉션이 있으면 속도가 느려짐
예를 들어, 10만 개의 리디렉션 URL이 들어 있는 JSON 파일이 있다고 해보자.
이걸 Middleware에서 직접 불러오면, 매 요청마다 무거운 파일을 처리해야 하므로 속도가 느려짐.

2️⃣ 해결책: Bloom Filter 사용
Bloom Filter는 빠르게 데이터가 있는지 없는지만 판단할 수 있는 자료구조
💡 정확한 값을 반환하는 게 아니라 "있을 가능성이 있다" 정도만 확인 가능
💡 매우 가벼워서 속도가 빠름

➡ 이걸 Middleware에서 먼저 체크해서 빠른 필터링을 하고,
필요한 경우만 Route Handler에서 실제 파일을 확인하도록 하는 것!

middleware.ts (Bloom Filter 미들웨어 (빠른 체크))

import { NextResponse , NextRequest } from 'next/server'

import { ScalableBloomFilter } from 'bloom-filters'

import GeneratedBloomFilter from './redirects/bloom-filter.json'

type RedirectEntry = {
    destination : string
    permanent : boolean
}

// 생성된 JSON 파일에서 블룸 필터 초기화
const bloomFilter = ScalableBloomFilter.fromJSON(GeneratedBloomFilter as any)

export async function middleware(request : NextRequest) {
    // 들어오는 요청의 경로를 가져옵니다.
    const pathname = request.nextUrl.pathname

    // 경로가 블룸 필터에 있는지 확인합니다.
    if (bloomFilter.has(pathname)) {
        // 경로 관리를 위해 경로 이름을 전달합니다.
        const api = new URL(
            `/api/redirects?pathname=${encodeURIComponent(request.nextUrl.pathname)}`,
            request.nextUrl.origin
        )

        try {
            // 경로 관리자로부터 리디렉션 데이터를 가져옵니다.
            const redirectData = await fetch(api)

            if (redirectData.ok) {
                const redirectEntry : RedirectEntry | undefined =
                    await redirectData.json()

                if (redirectEntry) {
                    // 상태 코드를 결정합니다.
                    const statusCode = redirectEntry.permanent ? 308 : 307

                    // 목적지로 리디렉션합니다.
                    return NextResponse.redirect(redirectEntry.destination, statusCode)
                }
            }
        } catch (error) {
            console.error(error)
        }
    }

    // 리디렉션을 찾지 못했습니다. 리디렉션 없이 요청을 계속 진행합니다.
    return NextResponse.next()
}

 

app/api/redirects/route.ts (실제 리디렉션 처리)

import { NextRequest , NextResponse } from 'next/server'

// 리디렉션 설정이 포함된 JSON 파일을 가져옵니다.
import redirects from '@/app/redirects/redirects.json'

// 리디렉션 엔트리의 타입을 정의합니다.
type RedirectEntry = {
    destination: string // 리디렉션될 대상 URL
    permanent: boolean   // 영구 리디렉션 여부
}

// GET 요청을 처리하는 함수입니다.
export function GET (request: NextRequest) {
    // 요청의 쿼리 매개변수에서 'pathname'을 가져옵니다.
    const pathname = request.nextUrl.searchParams.get('pathname')

    // 'pathname'이 제공되지 않은 경우, 400 Bad Request를 반환합니다.
    if (!pathname) {
        return new Response('Bad Request', { status: 400 })
    }

    // redirects.json 파일에서 해당 pathname에 대한 리디렉션 엔트리를 가져옵니다.
    const redirect = (redirects as Record<string, RedirectEntry>)[pathname]

    // Bloom 필터의 허위 긍정 결과를 고려하여 리디렉션이 없으면 400 반환합니다.
    if (!redirect) {
        return new Response('No redirect', { status: 400 })
    }

    // 리디렉션 엔트리를 JSON 형식으로 반환합니다.
    return NextResponse.json(redirect)
}

🔎 Good to know

  • 블룸 필터를 생성하려면 bloom-filters와 같은 라이브러리를 사용할 수 있다.
  • 경로 핸들러에 대한 요청을 검증하여 악성 요청을 방지해야 한다.

결론

이와 같이 Next.js에서는 다양한 방법으로 리디렉션을 관리할 수 있으며, 규모에 따라 적절한 방법을 선택하는 것이 중요하다.

 

 

 


출처

https://nextjs.org/docs/app/building-your-application/routing/redirectin

 

Routing: Redirecting | Next.js

Learn the different ways to handle redirects in Next.js.

nextjs.org

 

반응형