페이지 접근마다 반복되는 로그인 체크, Next.js proxy.ts로 한 곳에서 관리하기

soleil_lucy·2026년 4월 12일

글을 쓰게 된 이유

프로젝트를 진행하다가 로그인 여부에 따라 페이지 접근을 제어하는 기능을 구현해야 했습니다. 처음에는 각 페이지 컴포넌트마다 로그인 체크 로직을 넣어야 하나 했는데, 보호해야 할 페이지가 여러 개이고 한 곳에서 관리하는 게 효율적이라는 생각이 들었습니다. 찾아보니 이런 공통 로직은 미들웨어로 한 곳에서 처리하는 게 일반적이었고, Next.js 16에서는 proxy.ts 파일이 그 역할을 한다는 것을 알게 되었습니다. 이 글은 그 과정에서 공부한 내용을 정리한 글입니다.

문제: 로그인 체크 로직, 어디에 써야 할까?

제가 만든 서비스는 로그인한 사용자만 LLM 레시피 추출 기능을 사용하고, 마이페이지에서 레시피를 관리할 수 있도록 구현하고 싶었습니다.

로그인 여부를 체크해야 하는 페이지는 아래와 같습니다 — 사실상 서비스의 모든 페이지입니다.

  • 사용자가 추출하고 싶은 레시피 URL을 입력하는 메인 페이지
  • 레시피 추출 결과를 확인하는 페이지
  • 레시피를 단계별로 안내하는 요리 화면
  • 마이페이지 — 요리 기록을 확인하는 페이지
  • 마이페이지 — 요리 결과 통계 페이지
  • 마이페이지 — 냉장고 재료 재고 관리 페이지
  • 마이페이지 — 추출한 레시피 관리 페이지
  • 마이페이지 — 개인 설정 페이지

각 페이지 컴포넌트마다 로그인 체크 로직을 작성한다면 동일한 코드가 여러 곳에 흩어지고, 나중에 수정할 때도 모든 파일을 일일이 찾아가야 하는 문제가 생깁니다. 한 곳에서 관리할 수 있는 방법이 필요하다고 생각했습니다.

해결 방안: 미들웨어로 인증 체크를 한 곳에서 처리하기

한 곳에서 관리할 수 있는 방법을 찾다가 미들웨어라는 개념이 떠올랐습니다. 미들웨어란 요청이 실제로 처리되기 전에 실행되는 중간 레이어입니다. 모든 페이지 요청이 미들웨어를 거쳐서 들어오기 때문에, 이 영역에서 로그인 여부를 체크하는 로직을 관리하면 좋겠다는 생각을 했습니다.

그래서 Next.js에서는 미들웨어를 어떻게 작성하는지 공식 문서를 찾아봤습니다.

Proxy | Next.js 공식 문서

proxy.ts란?

Next.js 16부터 기존의 middleware.tsproxy.ts로 이름이 바뀌었습니다. 기능은 동일하고, 이름이 역할을 더 잘 반영하도록 변경된 것입니다. proxy.ts는 요청이 완료되기 전에 코드를 실행할 수 있게 해주는 파일로, 요청을 가로채서 리다이렉트, 리라이트, 헤더 수정 등을 처리할 수 있습니다.

proxy.ts 설명 시퀀스 다이어그램

proxy.ts를 사용하는 경우

공식 문서에서는 아래와 같은 상황에서 사용을 권장합니다.

  • 모든 페이지 또는 일부 페이지의 헤더를 수정해야 할 때
  • A/B 테스트처럼 사용자 그룹에 따라 다른 페이지를 보여줘야 할 때
  • 요청 정보를 기반으로 프로그래밍 방식의 리다이렉트가 필요할 때

proxy.ts 사용을 지양해야 하는 경우

proxy.ts는 모든 요청마다 실행되기 때문에 여기서 무거운 작업을 하면 전체 페이지 로딩 성능에 영향을 줍니다. 공식 문서에서 명시적으로 금지하는 경우는 아래와 같습니다.

  • DB 조회나 외부 API 호출 같은 느린 데이터 페칭
  • 완전한 세션 관리나 인증 솔루션으로의 사용
  • fetch에서 cache, revalidate, tags 옵션 사용

복잡한 로직은 API Routes나 Server Component에서 처리하는 것이 적절합니다.

proxy.ts에 로그인 체크 로직을 구현해도 될까?

제가 구현하려는 것은 쿠키에 저장된 토큰의 존재 여부만 확인하는 가벼운 체크입니다. DB 조회나 외부 API 호출 없이 빠르게 판단할 수 있기 때문에 적절한 케이스라고 판단해 proxy.ts에 로그인 체크 로직을 구현하기로 결정했습니다.

결과

아래는 실제 프로젝트에 적용한 proxy.ts 코드입니다.
실제 코드 보러가기

import { withAuth } from 'next-auth/middleware';
import { NextResponse } from 'next/server';

export default withAuth(
  function middleware(req) {
    const { token } = req.nextauth;
    const { pathname } = req.nextUrl;

    // 인증은 되었으나 추가 정보 입력(isComplete)이 안 된 경우 /signup으로 리다이렉트
    if (token && !token.isComplete && pathname !== '/signup') {
      return NextResponse.redirect(new URL('/signup', req.url));
    }

    return NextResponse.next();
  },
  {
    callbacks: {
      authorized: ({ token }) => !!token,
    },
    pages: {
      signIn: '/login',
    },
  },
);

// 보호할 경로 목록 — /login, /api/*, /docs, /_next 는 제외
export const config = {
  matcher: ['/((?!login|api|docs|_next/static|_next/image|favicon.ico).*)'],
};

config.matcher로 proxy.ts가 실행될 경로를 지정하고, next-auth의 withAuth를 사용해 토큰 존재 여부를 확인합니다. 토큰이 없으면 자동으로 /login으로 리다이렉트되고, 토큰은 있지만 추가 정보 입력(isComplete)이 완료되지 않은 경우에는 /signup으로 보냅니다.

각 페이지 컴포넌트마다 로그인 체크 로직을 반복해서 작성하는 대신, 미리 더 나은 방법을 찾아보고 싶었습니다. proxy.ts를 도입한 덕분에 처음부터 로그인 체크 로직을 한 곳에서 관리할 수 있게 됐고, 보호할 경로를 추가하거나 변경할 때도 proxy.ts만 수정하면 됩니다.

참고 자료

profile
여행과 책을 좋아하는 개발자입니다.

0개의 댓글