Redirecting
Next.js에서 리디렉션을 처리하는 여러 가지 방법이 있습니다. 이 페이지에서는 각 옵션, 사용 사례 및 대규모 리디렉션을 관리하는 방법을 설명합니다.
API | Purpose | Where | Status Code |
---|---|---|---|
useRouter | Perform a client-side navigation | Components | N/A |
redirects in next.config.js | Redirect an incoming request based on a path | next.config.js file | 307 (Temporary) or 308 (Permanent) |
NextResponse.redirect | Redirect an incoming request based on a condition | Middleware | Any |
useRouter()
hook
useRouter
훅
컴포넌트 내에서 리디렉션이 필요한 경우 useRouter
훅의 push
메서드를 사용할 수 있습니다. 예를 들어:
import { useRouter } from 'next/router'
export default function Page() {
const router = useRouter()
return (
<button type="button" onClick={() => router.push('/dashboard')}>
Dashboard
</button>
)
}
import { useRouter } from 'next/router'
export default function Page() {
const router = useRouter()
return (
<button type="button" onClick={() => router.push('/dashboard')}>
Dashboard
</button>
)
}
Good to know:
- 프로그래밍 방식으로 사용자를 내비게이트할 필요가 없다면
<Link>
컴포넌트를 사용하는 것이 좋습니다.
자세한 내용은 useRouter
API 레퍼런스를 참조하세요.
redirects
in next.config.js
next.config.js
의 redirects
next.config.js
파일의 redirects
옵션을 사용하면 들어오는 요청 경로를 다른 목적지 경로로 리디렉션할 수 있습니다. 이는 페이지의 URL 구조를 변경하거나 미리 알려진 리디렉션 목록이 있는 경우 유용합니다.
redirects
는 경로, 헤더, 쿠키 및 쿼리 매칭을 지원하여 들어오는 요청을 기반으로 사용자를 리디렉션할 수 있는 유연성을 제공합니다.
redirects
를 사용하려면 next.config.js
파일에 옵션을 추가하세요:
module.exports = {
async redirects() {
return [
// Basic redirect
{
source: '/about',
destination: '/',
permanent: true,
},
// Wildcard path matching
{
source: '/blog/:slug',
destination: '/news/:slug',
permanent: true,
},
]
},
}
자세한 내용은 redirects
API 레퍼런스를 참조하세요.
Good to know:
redirects
는permanent
옵션과 함께 307(Temporary Redirect) 또는 308(Permanent Redirect) 상태 코드를 반환할 수 있습니다.redirects
는 플랫폼에 제한이 있을 수 있습니다. 예를 들어, Vercel에서는 1,024개의 리디렉션 제한이 있습니다. 대규모 리디렉션(1000+ 개)을 관리하려면 Middleware를 사용하여 사용자 지정 솔루션을 만드는 것을 고려하세요. 자세한 내용은 대규모 리디렉션 관리를 참조하세요.redirects
는 Middleware 이전에 실행됩니다.
NextResponse.redirect
in Middleware
Middleware에서 NextResponse.redirect
Middleware는 요청이 완료되기 전에 코드를 실행할 수 있게 합니다. 그런 다음 들어오는 요청을 기반으로 다른 URL로 리디렉션할 수 있습니다. 이는 조건(예: 인증, 세션 관리 등)에 따라 사용자를 리디렉션하거나 대규모 리디렉션이 있는 경우 유용합니다.
예를 들어, 사용자가 인증되지 않은 경우 /login
페이지로 리디렉션하려면 다음과 같이 합니다:
import { NextResponse, NextRequest } from 'next/server'
import { authenticate } from 'auth-provider'
export function middleware(request: NextRequest) {
const isAuthenticated = authenticate(request)
// If the user is authenticated, continue as normal
if (isAuthenticated) {
return NextResponse.next()
}
// Redirect to login page if not authenticated
return NextResponse.redirect(new URL('/login', request.url))
}
export const config = {
matcher: '/dashboard/:path*',
}
import { NextResponse } from 'next/server'
import { authenticate } from 'auth-provider'
export function middleware(request) {
const isAuthenticated = authenticate(request)
// If the user is authenticated, continue as normal
if (isAuthenticated) {
return NextResponse.next()
}
// Redirect to login page if not authenticated
return NextResponse.redirect(new URL('/login', request.url))
}
export const config = {
matcher: '/dashboard/:path*',
}
Good to know:
- Middleware는
next.config.js
의redirects
이후 및 렌더링 이전에 실행됩니다.
자세한 내용은 Middleware 문서를 참조하세요.
Managing redirects at scale (advanced)
대규모 리디렉션 관리 (고급)
대규모 리디렉션(1000+ 개)을 관리하려면 Middleware를 사용하여 사용자 지정 솔루션을 만드는 것을 고려할 수 있습니다. 이를 통해 애플리케이션을 다시 배포하지 않고도 프로그램적으로 리디렉션을 처리할 수 있습니다.
이를 위해 고려해야 할 사항은 다음과 같습니다:
- 리디렉션 맵 생성 및 저장.
- 데이터 조회 성능 최적화.
Next.js 예제: Middleware with Bloom filter (opens in a new tab) 예제를 참조하여 아래 권장 사항을 구현하세요.
1. Creating and storing a redirect map
리디렉션 맵 생성 및 저장
리디렉션 맵은 데이터베이스(보통 키-값 저장소) 또는 JSON 파일에 저장할 수 있는 리디렉션 목록입니다.
다음 데이터 구조를 고려하세요:
{
"/old": {
"destination": "/new",
"permanent": true
},
"/blog/post-old": {
"destination": "/blog/post-new",
"permanent": true
}
}
Middleware에서 Vercel의 Edge Config (opens in a new tab) 또는 Redis (opens in a new tab)와 같은 데이터베이스에서 읽고, 들어오는 요청을 기반으로 사용자를 리디렉션할 수 있습니다:
import { NextResponse, NextRequest } from 'next/server'
import { get } from '@vercel/edge-config'
type RedirectEntry = {
destination: string
permanent: boolean
}
export async function middleware(request: NextRequest) {
const pathname = request.nextUrl.pathname
const redirectData = await get(pathname)
if (redirectData && typeof redirectData === 'string') {
const redirectEntry: RedirectEntry = JSON.parse(redirectData)
const statusCode = redirectEntry.permanent ? 308 : 307
return NextResponse.redirect(redirectEntry.destination, statusCode)
}
// No redirect found, continue without redirecting
return NextResponse.next()
}
import { NextResponse } from 'next/server'
import { get } from '@vercel/edge-config'
export async function middleware(request) {
const pathname = request.nextUrl.pathname
const redirectData = await get(pathname)
if (redirectData) {
const redirectEntry = JSON.parse(redirectData)
const statusCode = redirectEntry.permanent ? 308 : 307
return NextResponse.redirect(redirectEntry.destination, statusCode)
}
// No redirect found, continue without redirecting
return NextResponse.next()
}
2. Optimizing data lookup performance
데이터 조회 성능 최적화
들어오는 요청마다 대규모 데이터셋을 읽는 것은 느리고 비용이 많이 들 수 있습니다. 데이터 조회 성능을 최적화할 수 있는 두 가지 방법은 다음과 같습니다:
- Vercel Edge Config (opens in a new tab) 또는 Redis (opens in a new tab)와 같이 빠른 읽기를 위해 최적화된 데이터베이스를 사용합니다.
- Bloom 필터 (opens in a new tab)와 같은 데이터 조회 전략을 사용하여 더 큰 리디렉션 파일이나 데이터베이스를 읽기 전에 리디렉션이 존재하는지 효율적으로 확인합니다.
이전 예제를 고려하여 생성된 bloom 필터 파일을 Middleware에 가져온 다음, 들어오는 요청 경로가 bloom 필터에 존재하는지 확인할 수 있습니다.
만약 그렇다면, 요청을 API Routes로 전달하여 실제 파일을 확인하고 사용자를 적절한 URL로 리디렉션합니다. 이는 대규모 리디렉션 파일을 Middleware에 가져오는 것을 방지하여 들어오는 모든 요청을 느리게 만들 수 있습니다.
import { NextResponse, NextRequest } from 'next/server'
import { ScalableBloomFilter } from 'bloom-filters'
import GeneratedBloomFilter from './redirects/bloom-filter.json'
type RedirectEntry = {
destination: string
permanent: boolean
}
// Initialize bloom filter from a generated JSON file
const bloomFilter = ScalableBloomFilter.fromJSON(GeneratedBloomFilter as any)
export async function middleware(request: NextRequest) {
// Get the path for the incoming request
const pathname = request.nextUrl.pathname
// Check if the path is in the bloom filter
if (bloomFilter.has(pathname)) {
// Forward the pathname to the Route Handler
const api = new URL(
`/api/redirects?pathname=${encodeURIComponent(request.nextUrl.pathname)}`,
request.nextUrl.origin,
)
try {
// Fetch redirect data from the Route Handler
const redirectData = await fetch(api)
if (redirectData.ok) {
const redirectEntry: RedirectEntry | undefined =
await redirectData.json()
if (redirectEntry) {
// Determine the status code
const statusCode = redirectEntry.permanent ? 308 : 307
// Redirect to the destination
return NextResponse.redirect(redirectEntry.destination, statusCode)
}
}
} catch (error) {
console.error(error)
}
}
// No redirect found, continue the request without redirecting
return NextResponse.next()
}
import { NextResponse } from 'next/server'
import { ScalableBloomFilter } from 'bloom-filters'
import GeneratedBloomFilter from './redirects/bloom-filter.json'
// Initialize bloom filter from a generated JSON file
const bloomFilter = ScalableBloomFilter.fromJSON(GeneratedBloomFilter)
export async function middleware(request) {
// Get the path for the incoming request
const pathname = request.nextUrl.pathname
// Check if the path is in the bloom filter
if (bloomFilter.has(pathname)) {
// Forward the pathname to the Route Handler
const api = new URL(
`/api/redirects?pathname=${encodeURIComponent(request.nextUrl.pathname)}`,
request.nextUrl.origin,
)
try {
// Fetch redirect data from the Route Handler
const redirectData = await fetch(api)
if (redirectData.ok) {
const redirectEntry = await redirectData.json()
if (redirectEntry) {
// Determine the status code
const statusCode = redirectEntry.permanent ? 308 : 307
// Redirect to the destination
return NextResponse.redirect(redirectEntry.destination, statusCode)
}
}
} catch (error) {
console.error(error)
}
}
// No redirect found, continue the request without redirecting
return NextResponse.next()
}
Then, in the API Route:
import type { NextApiRequest, NextApiResponse } from 'next'
import redirects from '@/app/redirects/redirects.json'
type RedirectEntry = {
destination: string
permanent: boolean
}
export default function handler(req: NextApiRequest, res: NextApiResponse) {
const pathname = req.query.pathname
if (!pathname) {
return res.status(400).json({ message: 'Bad Request' })
}
// Get the redirect entry from the redirects.json file
const redirect = (redirects as Record<string, RedirectEntry>)[pathname]
// Account for bloom filter false positives
if (!redirect) {
return res.status(400).json({ message: 'No redirect' })
}
// Return the redirect entry
return res.json(redirect)
}
import redirects from '@/app/redirects/redirects.json'
export default function handler(req, res) {
const pathname = req.query.pathname
if (!pathname) {
return res.status(400).json({ message: 'Bad Request' })
}
// Get the redirect entry from the redirects.json file
const redirect = redirects[pathname]
// Account for bloom filter false positives
if (!redirect) {
return res.status(400).json({ message: 'No redirect' })
}
// Return the redirect entry
return res.json(redirect)
}
Good to know:
- bloom 필터를 생성하려면
bloom-filters
(opens in a new tab)와 같은 라이브러리를 사용할 수 있습니다.- 라우트 핸들러에 대한 요청을 유효성 검사하여 악의적인 요청을 방지해야 합니다.