-
미들웨어(프록시)로 유연하게 처리 해보기프론트엔드 2026. 6. 21. 22:31
다시 한번
- 더 해빗(TheHabit)이라는 습관 형성 챌린지 서비스를 다시 만들면서 겪은 이야기입니다.
- 온보딩 페이지를 구현하면서 고려하지 못한 부분에 대해서 보완해보고자 프로젝트를 다시 시작했습니다.

- 과거에 왜 하지 않았을까 생각해봤을때 딱 아래 사진과 똑같았습니다.
- 예외 처리 혹은 다른 여러가지 상황을 고려하지 못했던거 같습니다.
- 일단 작동하니 넘겼던 과거를 후회하면서...

- 여러 예외 상황 등을 바로 잡아보고자 합니다.

과거의 구현이 어떻게 되었고 현재 어떻게 수정할지에 대해
// 쿠키를 날것으로 직접 읽었음 const token = req.cookies.get('next-auth.session-token')?.value; console.log('pathname', pathname); // console.log도 남아있었음 if (token && pathname.startsWith('/onboarding')) { return NextResponse.redirect(new URL('/user/dashboard', req.url)); } if (!token && pathname.startsWith('/user')) { return NextResponse.redirect(new URL('/login', req.url)); } 온보딩 컴포넌트 // 완료 기록 자체가 없었음 const handleNext = () => { if (currentStep < ONBOARDING_LIST.length - 1) { setCurrentStep(currentStep + 1); } else { router.push('/login'); // 그냥 로그인 페이지로 보냄 } }; const onboarding = req.cookies.get('onboarding')?.value; if (onboarding === 'done' && isOnboardingPath) { // 근데 /dashboard 로 잘못 보내고 있었음 (버그) return NextResponse.redirect(new URL(token ? '/user/dashboard' : '/dashboard', req.url)); }- 과거에 만든 미들웨어 코드입니다. 지금와서 보니 많이 아쉽습니다..
- 일단 가장 큰 문제는 쿠키 형식으로 구현했었는데 문제는 DB에 저장을 하지 않아 유저가 개발자라면 쿠키에 데이터를 삭제시키면 문제가 생길거 같습니다.
- 또하 경로또한 팀원들끼리 정했던 규칙에서 바뀌는 상황이 발생해 이를 또한 수정이 필요해 보입니다.
개선점 찾아보기
1. 일단 가장 먼저 온보딩했다고 토큰이나 불린 값으로 DB에 저장하기
2. 미들웨어 경로 다시 수정해서 지정하기
🚨 여기서 주의할 점
1. 일단 가장 먼저 온보딩했다고 토큰이나 불린 값으로 DB에 저장하기 -> 2가지를 상황을 가정해야 합니다.
a. 유저가 온보딩을 봤는데 로그인을 안했다면?
b. 유저가 로그인을 하지 않고 온보딩을 봤는데 로그인을 한다면 그때의 온보딩 처리- 위와 같은 두가지 상황을 추가로 고려해서 생각해봤습니다.
두가지 해결해보자
// app/api/onboarding/complete/route.ts export async function POST() { const session = await getServerSession(authOptions); // 로그인 유저면 DB에 저장 (다른 경로) if (session?.user?.id) { await supabase .from('users') .update({ onboarding_completed: true }) .eq('id', session.user.id); return NextResponse.json({ redirectTo: '/user/dashboard' }); } // ✅ 비로그인이면 쿠키를 심는다 const response = NextResponse.json({ redirectTo: '/demo' }); response.cookies.set('onboarding', 'done', { httpOnly: true, // JS에서 접근 불가 (보안) secure: process.env.NODE_ENV === 'production', sameSite: 'lax', maxAge: 60 * 60 * 24 * 365, // 1년 path: '/', }); return response; }- 저는 다음과 같이 해결해봤습니다.
- 로그인을 한 유저가 온보딩을 완료시 : 기존 수퍼베이스 users 테이블에 온보딩을 완료하면 onboarding_completed : true로 DB에 저장을 하고 리다이렉트를 시켰습니다.
- 로그인을 하지 않은 유저가 온보딩을 완료시 : 쿠키에 onboarding:done 이라는 값을 넣고 온보딩을 한번 보면 더 이상 접근을 하지 않도록 쿠키에 데이터를 넣었습니다.
- 다음으로는 미들웨어 처리입니다.
// middleware.ts export default async function middleware(req: NextRequest) { const token = await getToken({ req, secret: process.env.NEXTAUTH_SECRET }); const onboardingCookie = req.cookies.get('onboarding')?.value; const isLoggedIn = !!token?.id; const hasGuestOnboardingCookie = onboardingCookie === 'done'; // ✅ 핵심: 로그인 됨 + 쿠키 있음 → DB에 동기화 if (isLoggedIn && hasGuestOnboardingCookie) { await syncGuestOnboardingToDb(token.id); } // ...이후 리다이렉트 로직 } // libs/onboarding/onboarding.server.ts export async function syncGuestOnboardingToDb(userId: string) { const supabase = getSupabaseAdmin(); await supabase .from('users') .update({ onboarding_completed: true, updated_at: new Date().toISOString(), }) .eq('id', userId); } 그리고 동기화 후 쿠키는 응답에서 삭제합니다. // middleware.ts 의 respond 함수 const respond = (response: NextResponse) => { if (isLoggedIn && hasGuestOnboardingCookie) { response.cookies.delete('onboarding'); // ✅ 쿠키 삭제 } return response; }; // middleware.ts // 로그인 됨 + 쿠키 있음 → DB에 동기화 if (isLoggedIn && hasGuestOnboardingCookie) { await syncGuestOnboardingToDb(token.id); // ← 이 부분 } // libs/onboarding/onboarding.server.ts export async function syncGuestOnboardingToDb(userId: string) { await supabase .from('users') .update({ onboarding_completed: true }) .eq('id', userId); } // 과거 export const config = { matcher: ['/', '/user/:path*', '/onboarding/:path*'], }; // 현재 export const config = { matcher: ['/', '/user/:path*', '/onboarding/:path*', '/login'], };- 미들웨어로 유저가 경로에 접근할려고 할때를 제어해봤습니다.
- 게스트 때 온보딩을 완료한 사용자가 나중에 로그인하면 미들웨어가 쿠키를 감지해서 DB에 자동으로 동기화하고 쿠키를 삭제합니다.
미들웨어 ??
- 미들웨어란 요청과 응답 사이에 끼어드는 코드라고 생각합니다.
- 이해는 되는데 이거를 왜 사용하는지는 예시를 봅시다.
- 아래 코드를 보면 세션으로 유저가 로그인했는지 파악하고 로그인이 안되어 있다면 UseEffect 안의 조건문이 실행되어 login으로 이동을 합니다.미들웨어 없이 각 페이지에서 직접 처리하면 이렇게 됩니다. // 모든 protected 페이지마다 이 코드가 반복됨 const { data: session } = useSession(); useEffect(() => { if (!session) router.replace('/login'); }, [session]);
- 여기서 하나의 문제가 발생하는데 우선 화면이 바로 이동하는게 아니라 리다이렉트가 일어나 화면이 나오고 그 다음 이동을 한다는 점입니다. 그리고 만약 위와 같이 다른 곳도 로그인이 되었는지 일일히 처리하기 위해서 위 코드를 반복적으로 호출해야 한다는 문제도 있습니다.
- 이때 미들웨어를 사용해서 처리하면 좋습니다.
- 하나 주의사항이 있는데 미들웨어는 기본적으로 matcher 경로를 따로 지정해주지 않으면 어떤 페이지에서든 미들웨어가 먼저 실행이 되어 성능의 저하가 생길수도 있습니다.
- 그래서 다음과 같이 특정 미들웨어가 필요한 경로에만 다음과 같이 작성해주는 것이 올바른 방법입니다.export const config = { matcher: ['/', '/user/:path*', '/onboarding/:path*', '/login'], };마무리
작동은 했지만 구멍이 있던 코드를 다시 들여다보니 사실 당연히 고려해야 했던 상황들이었습니다. 이번 개선을 통해 온보딩 완료 여부를 DB와 쿠키 두 곳에서 관리하고 미들웨어 하나로 모든 경우를 제어하게 됐습니다. 다음엔 처음 설계할 때부터 이런 예외 상황을 먼저 생각해보려 합니다.
'프론트엔드' 카테고리의 다른 글
Next.js 하이드레이션 에러 우아하게 처리하기 (0) 2026.06.22 프로젝트를 다방면으로 생각해보기 (0) 2026.06.14 클라이언트 중복 방어의 한계와 서버 이중 방어 — isSubmitting부터 idempotency key까지 (0) 2026.06.07 AbortController 도입시도 (0) 2026.06.01 React Activity? (0) 2026.03.12