리다이렉트
Next.js에서 리다이렉트를 처리하는 방법에는 여러 가지가 있습니다. 이 페이지에서는 사용 가능한 각 옵션, 사용 사례, 그리고 많은 수의 리다이렉트를 관리하는 방법에 대해 살펴보겠습니다.
API | 목적 | 위치 | 상태 코드 |
---|---|---|---|
redirect | 데이터 변경 또는 이벤트 후 사용자 리다이렉트 | 서버 컴포넌트, 서버 액션, 라우트 핸들러 | 307 (임시) 또는 303 (서버 액션) |
permanentRedirect | 데이터 변경 또는 이벤트 후 사용자 리다이렉트 | 서버 컴포넌트, 서버 액션, 라우트 핸들러 | 308 (영구) |
useRouter | 클라이언트 측 경로 이동 처리 | 클라이언트 컴포넌트의 이벤트 핸들러 | N/A |
next.config.js 에서의 redirects | 경로를 기반으로 들어오는 요청 리다이렉트 | next.config.js 파일 | 307 (임시) 또는 308 (영구) |
NextResponse.redirect | 조건에 따라 들어오는 요청 리다이렉트 | 미들웨어 | 모든 상태 코드 |
redirect
함수
redirect
함수를 사용하면 사용자를 다른 URL로 리다이렉트할 수 있습니다. 서버 컴포넌트, 라우트 핸들러, 그리고 서버 액션에서 redirect
를 호출할 수 있습니다.
redirect
는 주로 데이터 변경이나 이벤트 후에 사용됩니다. 예를 들어, 게시물을 생성한 후:
"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}`); // 새 게시물 페이지로 이동
}
"use server";
import { redirect } from "next/navigation";
import { revalidatePath } from "next/cache";
export async function createPost(id) {
try {
// 데이터베이스 호출
} catch (error) {
// 오류 처리
}
revalidatePath("/posts"); // 캐시된 게시물 업데이트
redirect(`/post/${id}`); // 새 게시물 페이지로 이동
}
알아두면 좋은 점:
redirect
는 기본적으로 307 (임시 리다이렉트) 상태 코드를 반환합니다. 서버 액션에서 사용될 때는 303 (See Other)을 반환하며, 이는 일반적으로 POST 요청의 결과로 성공 페이지로 리다이렉트할 때 사용됩니다.redirect
는 내부적으로 오류를 발생시키므로try/catch
블록 외부에서 호출해야 합니다.redirect
는 렌더링 과정 중에 클라이언트 컴포넌트에서 호출할 수 있지만 이벤트 핸들러에서는 사용할 수 없습니다. 대신useRouter
훅(hook)을 사용할 수 있습니다.redirect
는 절대 URL도 허용하며 외부 링크로 리다이렉트하는 데 사용할 수 있습니다.- 렌더링 과정 전에 리다이렉트하려면
next.config.js
또는 미들웨어를 사용하세요.
자세한 정보는 redirect
API 참조를 참조하세요.
permanentRedirect
함수
permanentRedirect
함수를 사용하면 사용자를 다른 URL로 영구적으로 리다이렉트할 수 있습니다. 서버 컴포넌트, 라우트 핸들러, 그리고 서버 액션에서 permanentRedirect
를 호출할 수 있습니다.
permanentRedirect
는 주로 엔티티의 정식 URL을 변경하는 데이터 변경이나 이벤트 후에 사용됩니다. 예를 들어, 사용자가 사용자 이름을 변경한 후 프로필 URL을 업데이트할 때:
"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}`); // 새 사용자 프로필로 이동
}
"use server";
import { permanentRedirect } from "next/navigation";
import { revalidateTag } from "next/cache";
export async function updateUsername(username, formData) {
try {
// 데이터베이스 호출
} catch (error) {
// 오류 처리
}
revalidateTag("username"); // 사용자 이름에 대한 모든 참조 업데이트
permanentRedirect(`/profile/${username}`); // 새 사용자 프로필로 이동
}
알아두면 좋은 점:
permanentRedirect
는 기본적으로 308 (영구 리다이렉트) 상태 코드를 반환합니다.permanentRedirect
는 절대 URL도 허용하며 외부 링크로 리다이렉트하는 데 사용할 수 있습니다.- 렌더링 과정 전에 리다이렉트하려면
next.config.js
또는 미들웨어를 사용하세요.
자세한 정보는 permanentRedirect
API 참조를 참조하세요.
useRouter()
훅(Hook)
클라이언트 컴포넌트의 이벤트 핸들러 내에서 리다이렉트해야 하는 경우, 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>
);
}
"use client";
import { useRouter } from "next/navigation";
export default function Page() {
const router = useRouter();
return (
<button type="button" onClick={() => router.push("/dashboard")}>
대시보드
</button>
);
}
알아두면 좋은 점:
- 프로그래밍 방식으로 사용자를 네비게이트할 필요가 없다면
<Link>
컴포넌트를 사용해야 합니다.
자세한 정보는 useRouter
API 참조를 참조하세요.
next.config.js
의 redirects
next.config.js
파일의 redirects
옵션을 사용하면 들어오는 요청 경로를 다른 목적지 경로로 리다이렉트할 수 있습니다. 이는 페이지의 URL 구조를 변경하거나 미리 알려진 리다이렉트 목록이 있을 때 유용합니다.
redirects
는 경로, 헤더, 쿠키, 쿼리 매칭을 지원하여 들어오는 요청에 따라 사용자를 리다이렉트할 수 있는 유연성을 제공합니다.
redirects
를 사용하려면 next.config.js
파일에 옵션을 추가하세요:
module.exports = {
async redirects() {
return [
// 기본 리다이렉트
{
source: "/about",
destination: "/",
permanent: true,
},
// 와일드카드 경로 매칭
{
source: "/blog/:slug",
destination: "/news/:slug",
permanent: true,
},
];
},
};
자세한 정보는 redirects
API 참조를 참조하세요.
알아두면 좋은 점:
redirects
는permanent
옵션을 통해 307 (임시 리다이렉트) 또는 308 (영구 리다이렉트) 상태 코드를 반환할 수 있습니다.redirects
는 플랫폼에 따라 제한이 있을 수 있습니다. 예를 들어, Vercel에서는 1,024개의 리다이렉트 제한이 있습니다. 많은 수의 리다이렉트(1000+)를 관리하려면 미들웨어를 사용하여 사용자 정의 솔루션을 만드는 것을 고려해보세요. 자세한 내용은 대규모 리다이렉트 관리를 참조하세요.redirects
는 미들웨어 전에 실행됩니다.
미들웨어의 NextResponse.redirect
미들웨어를 사용하면 요청이 완료되기 전에 코드를 실행할 수 있습니다. 그런 다음 들어오는 요청에 따라 NextResponse.redirect
를 사용하여 다른 URL로 리다이렉트할 수 있습니다. 이는 조건(예: 인증, 세션 관리 등)에 따라 사용자를 리다이렉트하거나 대량의 리다이렉트가 있는 경우에 유용합니다.
예를 들어, 사용자가 인증되지 않은 경우 /login
페이지로 리다이렉트하려면:
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*",
};
import { NextResponse } from "next/server";
import { authenticate } from "auth-provider";
export function middleware(request) {
const isAuthenticated = authenticate(request);
// 사용자가 인증된 경우 정상적으로 계속 진행
if (isAuthenticated) {
return NextResponse.next();
}
// 인증되지 않은 경우 로그인 페이지로 리다이렉트
return NextResponse.redirect(new URL("/login", request.url));
}
export const config = {
matcher: "/dashboard/:path*",
};
알아두면 좋은 점:
- 미들웨어는
next.config.js
의redirects
후에 그리고 렌더링 전에 실행됩니다.
자세한 정보는 미들웨어 문서를 참조하세요.
대규모 리다이렉트 관리 (고급)
많은 수의 리다이렉트(1000+)를 관리하려면 미들웨어를 사용하여 사용자 정의 솔루션을 만드는 것을 고려해볼 수 있습니다. 이를 통해 애플리케이션을 다시 배포하지 않고도 프로그래밍 방식으로 리다이렉트를 처리할 수 있습니다.
이를 위해 다음 사항을 고려해야 합니다:
- 리다이렉트 맵 생성 및 저장.
- 데이터 조회 성능 최적화.
Next.js 예제: 아래 권장 사항의 구현에 대해서는 블룸 필터를 사용한 미들웨어 예제를 참조하세요.
1. 리다이렉트 맵 생성 및 저장
리다이렉트 맵은 데이터베이스(일반적으로 키-값 저장소) 또는 JSON 파일에 저장할 수 있는 리다이렉트 목록입니다.
다음과 같은 데이터 구조를 고려해보세요:
{
"/old": {
"destination": "/new",
"permanent": true
},
"/blog/post-old": {
"destination": "/blog/post-new",
"permanent": true
}
}
미들웨어에서 Vercel의 Edge Config 또는 Redis와 같은 데이터베이스에서 읽고 들어오는 요청에 따라 사용자를 리다이렉트할 수 있습니다:
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);
}
// 리다이렉트를 찾지 못한 경우, 리다이렉트하지 않고 계속 진행
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);
}
// 리다이렉트를 찾지 못한 경우, 리다이렉트하지 않고 계속 진행
return NextResponse.next();
}
2. 데이터 조회 성능 최적화
모든 들어오는 요청에 대해 대규모 데이터셋을 읽는 것은 느리고 비용이 많이 들 수 있습니다. 데이터 조회 성능을 최적화하는 두 가지 방법이 있습니다:
- Vercel Edge Config 또는 Redis와 같이 빠른 읽기에 최적화된 데이터베이스를 사용합니다.
- 블룸 필터와 같은 데이터 조회 전략을 사용하여 더 큰 리다이렉트 파일이나 데이터베이스를 읽기 전에 리다이렉트가 존재하는지 효율적으로 확인합니다.
이전 예제를 고려하여, 생성된 블룸 필터 파일을 미들웨어로 가져온 다음, 들어오는 요청 경로가 블룸 필터에 존재하는지 확인할 수 있습니다.
존재한다면, 요청을 라우트 핸들러로 전달하여 실제 파일을 확인하고 사용자를 적절한 URL로 리다이렉트합니다. 이렇게 하면 모든 들어오는 요청을 느리게 만들 수 있는 큰 리다이렉트 파일을 미들웨어로 가져오는 것을 피할 수 있습니다.
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();
}
import { NextResponse } from "next/server";
import { ScalableBloomFilter } from "bloom-filters";
import GeneratedBloomFilter from "./redirects/bloom-filter.json";
// 생성된 JSON 파일에서 블룸 필터 초기화
const bloomFilter = ScalableBloomFilter.fromJSON(GeneratedBloomFilter);
export async function middleware(request) {
// 들어오는 요청의 경로 가져오기
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 = 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();
}
그런 다음 라우트 핸들러에서:
import { NextRequest, NextResponse } from "next/server";
import redirects from "@/app/redirects/redirects.json";
type RedirectEntry = {
destination: string;
permanent: boolean;
};
export function GET(request: NextRequest) {
const pathname = request.nextUrl.searchParams.get("pathname");
if (!pathname) {
return new Response("Bad Request", { status: 400 });
}
// redirects.json 파일에서 리다이렉트 항목 가져오기
const redirect = (redirects as Record<string, RedirectEntry>)[pathname];
// 블룸 필터 거짓 양성 처리
if (!redirect) {
return new Response("No redirect", { status: 400 });
}
// 리다이렉트 항목 반환
return NextResponse.json(redirect);
}
import { NextResponse } from "next/server";
import redirects from "@/app/redirects/redirects.json";
export function GET(request) {
const pathname = request.nextUrl.searchParams.get("pathname");
if (!pathname) {
return new Response("Bad Request", { status: 400 });
}
// redirects.json 파일에서 리다이렉트 항목 가져오기
const redirect = redirects[pathname];
// 블룸 필터 거짓 양성 처리
if (!redirect) {
return new Response("No redirect", { status: 400 });
}
// 리다이렉트 항목 반환
return NextResponse.json(redirect);
}
알아두면 좋은 점:
- 블룸 필터를 생성하려면
bloom-filters
와 같은 라이브러리를 사용할 수 있습니다.- 악의적인 요청을 방지하기 위해 라우트 핸들러로 보내는 요청을 검증해야 합니다.