카테고리 없음
[Next.js] 커스텀 도메인 멀티 테넌시(Custom Domain Multi-Tenancy)
순코딩
2025. 7. 26. 17:10
src / middleware.ts
// middleware.ts
import { NextRequest, NextResponse } from "next/server";
/**
* 특정 슬러그(id)에 해당하는 커스텀 도메인을 찾아주는 함수
* 예시 함수이며 실제 서비스에서는 supabase 데이터베이스에서 조회해야 함
*/
function getDomainByHost(host: string): number | null {
const domainMappings = [
{ id: 123, customDomain: "philog.site" },
{ id: 456, customDomain: "philog.site" },
{ id: 789, customDomain: "philog.site" },
];
const result = domainMappings.find((item) => String(item.customDomain) === host);
return result ? result.id : null;
}
export function middleware(request: NextRequest) {
// 우리가 운영하는 메인 도메인 (예: Vercel 프로젝트 기본 도메인)
const MAIN_DOMAIN = "ldk1009-test.shop";
// 커스텀 도메인에서 접근한 경우(philog.site로 접근)
const host = request.headers.get("host") || "";
// 요청 호스트가 커스텀 도메인이라면(philog.site)
if (!host.endsWith(MAIN_DOMAIN)) {
// 커스텀 도메인의 id 찾기
const slug = getDomainByHost(host);
// 커스텀 도메인이 없다면 메인 도메인의 404 페이지로 리다이렉트
if (!slug) {
return NextResponse.redirect(`https://${MAIN_DOMAIN}/404`);
}
// 사용자가 요청한 전체 URL 정보 복사
// 예: https://philog.site/ → { pathname: "/", host: "philog.site", ... }
const url = request.nextUrl.clone();
// 내부적으로 보여줄 경로를 슬러그 기반으로 변경
// 실제로 보여줄 콘텐츠는 ldk1009-test.shop/123 페이지임
url.pathname = `/${slug}`;
// 위에서 변경한 URL 경로로 내부적으로 rewrite 수행
// 주소창은 그대로 유지되며, Next.js는 /123/notice 페이지로 라우팅함
return NextResponse.rewrite(url);
}
return NextResponse.next();
}
// 정적 자산 요청은 제외
export const config = {
matcher: ["/((?!_next|favicon.ico).*)"],
};
app / [id] / page.tsx
type PropsType = Promise<{ id: string }>;
export default async function BunyangPage({ params }: { params: PropsType }) {
const { id } = await params;
return <div>분양 페이지 : {id}</div>;
}