정적 랜딩 성능을 유지하면서 사용자 상태 분기 처리, CTA → /me 패턴
랜딩 페이지 설계: 빠른 첫 화면과 정확한 분기, 둘 다 잡기
랜딩 페이지는 사용자가 서비스를 처음 만나는 지점이다.
그래서 두 가지 요구를 동시에 만족해야 한다.
최대한 빠르게 보여야 한다.
CTA 클릭 시 사용자 상태에 맞는 정확한 목적지로 이동해야 한다.
문제는 이 두 요구가 충돌한다는 점이다.
랜딩에서 바로 세션을 조회해 분기하면 목적지 결정은 쉬워지지만, 랜딩을 정적으로 유지하기 어려워진다.
반대로 랜딩을 완전 정적으로 두면 CTA 이후 분기 로직을 별도로 설계해야 한다.
해결 아이디어: CTA는 무조건 /me로 보낸다
핵심은 책임 분리다.
랜딩(
/)은 정적 UI만 담당/me는 서버 리다이렉트 전용 엔드포인트로 동작
이렇게 하면 랜딩은 끝까지 빠르게 유지하면서, 사용자 상태 기반 분기는 /me 한 곳에서 일관되게 처리할 수 있다.
"use client";
import Link from "next/link";
import { useRouter } from "next/navigation";
export default function CTAButton() {
const router = useRouter();
return (
<Link
href="/me"
prefetch={false}
onMouseEnter={() => router.prefetch("/me")}
>
Start for free
</Link>
);
}
/me에서 목적지를 단일 책임으로 처리
사용자가 CTA를 누르면 항상 /me로 이동하고, /me에서 세션/온보딩/핸들을 순차 판별해 최종 리다이렉트한다.
세션 없음 →
/sign-in세션 있음 + 온보딩 미완료 →
/onboarding온보딩 완료 + 정상 핸들 →
/${handle}핸들 없음/비정상 →
/onboarding
import { redirect } from "next/navigation";
export default async function MePage() {
const session = await getSession();
if (!session) redirect("/sign-in");
const onboardingComplete = isOnboardingComplete(session.user.userMetadata);
if (!onboardingComplete) redirect("/onboarding");
const primaryPage = await findPrimaryPageHandleByUserId(session.user.id);
if (!primaryPage?.handle?.startsWith("@")) {
redirect("/onboarding");
}
redirect(`/${primaryPage.handle}`);
}
이 구조의 핵심은 명확하다.랜딩 렌더링과 인증 기반 목적지 결정을 분리해, 각각의 관심사를 섞지 않는다.
프리패치 전략 개선
Next.js Link는 기본 자동 prefetch를 수행한다.
하지만 /me는 단순 정적 라우트가 아니라 세션 조회와 분기 판단이 있는 서버 페이지다.
자동 prefetch를 그대로 두면 사용자가 CTA를 누르지 않아도 불필요한 인증 관련 선행 요청이 발생할 수 있다.
그래서 다음 전략이 더 합리적이다.
기본 prefetch 비활성화 (
prefetch={false})클릭 가능성이 높아지는 시점(예: hover)에만 수동 prefetch
이렇게 하면 불필요한 선행 요청을 줄이면서도, 실제 클릭 직전 체감 속도는 유지할 수 있다.
트레이드오프와 운영 포인트
이 패턴에는 트레이드오프도 있다.
라우팅 홉 증가:
/ → /me → 최종 경로인증/온보딩 분기 로직이
/me에 집중따라서
/me의 안정성과 테스트가 중요
FCP를 측정하여 수치를 비교하면,
모바일 - 7.3% 개선 (2266ms → 2101ms)
데스크톱 - 3.6% 개선 (325ms → 313ms)
극적인 개선은 없지만 랜딩 성능(정적 유지)과 정책 관리 일관성(분기 단일화)을 함께 확보한다는 점에서 합리적인 선택이라고 생각한다. (CTA → /me 패턴)