Next.js 기초 입문 19주차: 미들웨어 (Middleware) 활용 심층 분석

📚
제19주차 학습 목표

미들웨어 (Middleware) 활용

요청이 처리되기 전에 실행되는 미들웨어를 사용하여 로직을 추가합니다.

Next.js 기초 입문 19회차: 미들웨어 (Middleware) 활용

이번 회차에서는 Next.js 애플리케이션의 핵심 기능 중 하나인 미들웨어(Middleware)에 대해 심도 있게 학습합니다. 미들웨어는 요청이 실제 페이지나 API 라우트로 전달되기 전에 실행되는 코드로, 애플리케이션의 동작을 유연하게 제어하고 확장하는 데 필수적인 역할을 수행합니다.

📌 이번 회차 학습 목표

  • Next.js 미들웨어의 개념과 필요성을 정확히 이해할 수 있습니다.
  • 미들웨어의 동작 원리와 실행 흐름을 설명할 수 있습니다.
  • middleware.ts (또는 .js) 파일을 생성하고 미들웨어를 구현할 수 있습니다.
  • 미들웨어를 활용하여 특정 경로에 대한 인증 및 리디렉션 로직을 구현할 수 있습니다.
  • 미들웨어 설정(matcher)을 통해 특정 경로에만 미들웨어를 적용하는 방법을 숙지할 수 있습니다.

📝 개념 설명: Next.js 미들웨어란 무엇인가?

Next.js의 미들웨어는 클라이언트로부터 서버로 들어오는 모든 요청에 대해 특정 로직을 실행할 수 있도록 하는 기능입니다. 이는 마치 문지기처럼 요청이 실제 목적지에 도달하기 전에 필요한 검사나 조작을 수행하는 역할을 합니다. 예를 들어, 사용자가 로그인되어 있는지 확인하거나, 특정 페이지에 대한 접근 권한을 검사하거나, 요청 헤더를 조작하는 등의 작업을 미들웨어에서 처리할 수 있습니다.

미들웨어의 동작 원리

Next.js 미들웨어는 _middleware.ts (또는 .js) 파일이 아닌, 프로젝트의 루트 레벨에 middleware.ts (또는 .js) 파일을 생성하여 정의합니다. 이 파일은 Next.js 서버가 시작될 때 로드되며, 정의된 로직은 모든 요청에 대해 실행됩니다. 미들웨어 함수는 NextRequest 객체를 인자로 받아 NextResponse 객체를 반환합니다. 이 NextResponse 객체를 통해 요청을 계속 진행시키거나, 리디렉션하거나, 응답을 조작할 수 있습니다.

🔄 미들웨어 요청 처리 흐름
클라이언트 요청
Next.js 미들웨어 실행
미들웨어 로직 처리 (인증, 리디렉션 등)
페이지/API 라우트 처리
클라이언트 응답

미들웨어의 주요 활용 사례

  • 인증(Authentication) 및 권한 부여(Authorization): 로그인 여부를 확인하고, 특정 역할(예: 관리자)을 가진 사용자만 접근할 수 있도록 제한합니다.
  • 리디렉션(Redirection): 특정 조건에 따라 다른 페이지로 사용자를 이동시킵니다. (예: 로그인하지 않은 사용자를 로그인 페이지로)
  • 로깅(Logging): 모든 요청에 대한 정보를 기록하여 디버깅 및 분석에 활용합니다.
  • A/B 테스팅: 사용자 그룹에 따라 다른 버전의 페이지를 제공합니다.
  • 국제화(Internationalization): 사용자의 언어 설정에 따라 적절한 언어 버전의 페이지를 제공합니다.
  • 헤더 조작: 요청 또는 응답 헤더를 추가, 수정, 삭제합니다.

💡 예제 & 실습: 미들웨어로 인증 및 리디렉션 구현하기

이번 실습에서는 Next.js 미들웨어를 사용하여 특정 경로(예: /dashboard)에 대한 접근을 인증된 사용자에게만 허용하고, 인증되지 않은 사용자는 로그인 페이지로 리디렉션하는 로직을 구현해보겠습니다.

1. 프로젝트 설정 및 페이지 생성

먼저, Next.js 프로젝트를 생성하고 필요한 페이지를 준비합니다.

npx create-next-app@latest nextjs-middleware-app
cd nextjs-middleware-app

다음으로, 세 개의 페이지를 생성합니다:

  • pages/index.tsx (홈 페이지)
  • pages/login.tsx (로그인 페이지)
  • pages/dashboard.tsx (보호된 페이지)

pages/index.tsx

// pages/index.tsx
import Link from 'next/link';

export default function HomePage() {
  return (
    <div>
      <h1>홈 페이지</h1>
      <p>환영합니다!</p>
      <ul>
        <li>
          <Link href='/login'>로그인 페이지로 이동</Link>
        </li>
        <li>
          <Link href='/dashboard'>대시보드로 이동 (보호된 페이지)</Link>
        </li>
      </ul>
    </div>
  );
}

pages/login.tsx

// pages/login.tsx
import { useRouter } from 'next/router';
import { useState } from 'react';

export default function LoginPage() {
  const router = useRouter();
  const [isLoggedIn, setIsLoggedIn] = useState(false);

  const handleLogin = () => {
    // 실제 환경에서는 서버에 로그인 요청을 보내고 토큰을 받습니다.
    // 여기서는 간단히 localStorage에 'loggedIn' 상태를 저장합니다.
    localStorage.setItem('loggedIn', 'true');
    setIsLoggedIn(true);
    alert('로그인되었습니다!');
    router.push('/dashboard'); // 로그인 후 대시보드로 이동
  };

  const handleLogout = () => {
    localStorage.removeItem('loggedIn');
    setIsLoggedIn(false);
    alert('로그아웃되었습니다!');
    router.push('/'); // 로그아웃 후 홈으로 이동
  };

  return (
    <div>
      <h1>로그인 페이지</h1>
      {!isLoggedIn ? (
        <button onClick={handleLogin}>로그인</button>
      ) : (
        <button onClick={handleLogout}>로그아웃</button>
      )}
      <p>현재 로그인 상태: {isLoggedIn ? '로그인됨' : '로그아웃됨'}</p>
    </div>
  );
}

pages/dashboard.tsx

// pages/dashboard.tsx
import Link from 'next/link';

export default function DashboardPage() {
  return (
    <div>
      <h1>대시보드</h1>
      <p>환영합니다! 로그인된 사용자만 볼 수 있는 페이지입니다.</p>
      <Link href='/'>홈으로 돌아가기</Link>
    </div>
  );
}

2. 미들웨어 파일 생성 및 구현

프로젝트 루트 디렉토리middleware.ts 파일을 생성합니다.

// middleware.ts
import { NextResponse } from 'next/server';
import type { NextRequest } from 'next/server';

// 이 미들웨어는 모든 요청에 대해 실행됩니다.
export function middleware(request: NextRequest) {
  const { pathname } = request.nextUrl;

  // 보호된 경로 목록
  const protectedPaths = ['/dashboard'];

  // 로그인 페이지 경로
  const loginPath = '/login';

  // 요청 경로가 보호된 경로 중 하나인지 확인
  const isProtectedPath = protectedPaths.some(path => pathname.startsWith(path));

  // 'loggedIn' 쿠키가 있는지 확인 (실제로는 JWT 토큰 등을 검사합니다)
  // 여기서는 간단히 'loggedIn'이라는 이름의 쿠키가 'true'인지 확인합니다.
  const isLoggedIn = request.cookies.get('loggedIn')?.value === 'true';

  // 보호된 경로에 접근하려 하지만 로그인되어 있지 않은 경우
  if (isProtectedPath && !isLoggedIn) {
    // 로그인 페이지로 리디렉션합니다.
    // 현재 URL을 query parameter로 넘겨주어 로그인 후 원래 페이지로 돌아갈 수 있도록 합니다.
    const url = request.nextUrl.clone();
    url.pathname = loginPath;
    url.searchParams.set('redirect', pathname);
    return NextResponse.redirect(url);
  }

  // 로그인 페이지에 접근하려 하지만 이미 로그인되어 있는 경우
  if (pathname === loginPath && isLoggedIn) {
    // 대시보드 페이지로 리디렉션합니다.
    const url = request.nextUrl.clone();
    url.pathname = '/dashboard';
    return NextResponse.redirect(url);
  }

  // 그 외의 경우, 요청을 계속 진행합니다.
  return NextResponse.next();
}

// 미들웨어가 실행될 경로를 지정합니다.
// 이 설정이 없으면 모든 경로에 대해 미들웨어가 실행됩니다.
export const config = {
  matcher: [
    /*
     * Match all request paths except for the ones starting with:
     * - _next/static (static files)
     * - _next/image (image optimization files)
     * - favicon.ico (favicon file)
     * - API routes (e.g., /api/...) if you don't want middleware to run on them
     */
    '/((?!api|_next/static|_next/image|favicon.ico).*)',
  ],
};

코드 해설

  1. middleware(request: NextRequest) 함수: 이 함수가 실제 미들웨어 로직을 포함합니다. NextRequest 객체는 들어오는 요청에 대한 정보를 제공하며, NextResponse 객체는 응답을 제어하는 데 사용됩니다.
  2. protectedPaths: 미들웨어로 보호할 경로들을 배열로 정의합니다. 여기서는 /dashboard 경로를 보호합니다.
  3. isLoggedIn: 요청 쿠키에서 'loggedIn' 값을 확인하여 사용자의 로그인 상태를 시뮬레이션합니다. 실제 애플리케이션에서는 JWT 토큰이나 세션 ID 등을 검증하는 로직이 들어갑니다.
  4. 리디렉션 로직:
    • isProtectedPath && !isLoggedIn: 보호된 경로에 접근하려 하지만 로그인되어 있지 않은 경우, /login 페이지로 리디렉션합니다. 이때 redirect 쿼리 파라미터를 사용하여 로그인 후 원래 가려던 페이지로 돌아갈 수 있도록 합니다.
    • pathname === loginPath && isLoggedIn: 이미 로그인되어 있는데 /login 페이지로 접근하려 하는 경우, /dashboard 페이지로 리디렉션하여 불필요한 로그인 시도를 방지합니다.
  5. NextResponse.next(): 미들웨어 로직이 완료된 후, 요청을 다음 단계(페이지 또는 API 라우트)로 계속 진행시킵니다.
  6. config.matcher: 이 설정은 매우 중요합니다. 미들웨어가 실행될 경로를 정규 표현식 또는 경로 배열로 지정합니다. 이 예제에서는 /api, _next/static, _next/image, favicon.ico를 제외한 모든 경로에 대해 미들웨어를 실행하도록 설정했습니다. 이는 불필요한 파일 요청에 미들웨어가 실행되는 것을 방지하여 성능을 최적화합니다.

3. 실행 및 테스트

애플리케이션을 실행합니다.

npm run dev

브라우저에서 http://localhost:3000에 접속합니다.

  • 먼저 /dashboard 링크를 클릭해보세요. 로그인되지 않은 상태이므로 /login 페이지로 리디렉션될 것입니다.
  • /login 페이지에서 ‘로그인’ 버튼을 클릭합니다. 이제 로그인 상태가 되고 /dashboard 페이지로 이동할 것입니다.
  • 다시 /login 페이지로 직접 이동해보세요. 이미 로그인되어 있으므로 /dashboard 페이지로 리디렉션될 것입니다.
  • ‘로그아웃’ 버튼을 클릭하면 로그인 상태가 해제되고 / (홈) 페이지로 이동합니다.
📌 핵심 포인트: 미들웨어의 역할
🔑
인증/권한
사용자 로그인 상태 확인 및 접근 권한 제어
➡️
리디렉션
특정 조건에 따라 다른 페이지로 사용자 이동
⚙️
요청 조작
요청 헤더, 쿠키 등 수정 및 추가
🎯
경로 필터링
matcher 설정을 통해 특정 경로에만 적용

⚠️ 자주 틀리는 것 / 주의사항

  • middleware.ts 파일 위치: 미들웨어 파일은 반드시 프로젝트의 루트 디렉토리 (pages, public 폴더와 같은 레벨)에 위치해야 합니다. pages 폴더 내에 두면 작동하지 않습니다.
  • matcher 설정의 중요성: config.matcher를 올바르게 설정하지 않으면, 정적 파일(_next/static), 이미지(_next/image), 아이콘(favicon.ico) 등 불필요한 모든 요청에 미들웨어가 실행되어 성능 저하를 초래할 수 있습니다. 필요한 경로에만 미들웨어가 실행되도록 세심하게 설정해야 합니다.
  • 클라이언트 컴포넌트와의 혼동: 미들웨어는 서버 사이드에서 요청이 처리되기 전에 실행됩니다. 따라서 브라우저의 window 객체나 document 객체에 접근할 수 없습니다.
  • 쿠키 사용 시 주의: 미들웨어에서는 NextRequest.cookies를 통해 요청 쿠키에 접근하고, NextResponse.cookies를 통해 응답 쿠키를 설정할 수 있습니다. 클라이언트 측 localStoragesessionStorage는 미들웨어에서 직접 접근할 수 없습니다.
  • 무한 리디렉션 루프: 미들웨어 로직을 잘못 작성하면 특정 조건에서 무한 리디렉션 루프에 빠질 수 있습니다. 예를 들어, 로그인되지 않은 사용자를 로그인 페이지로 리디렉션하는데, 로그인 페이지 자체도 미들웨어의 보호를 받는 경우 발생할 수 있습니다. matcher 설정이나 미들웨어 내부 로직에서 이를 방지해야 합니다.

🔗 다음 회차 예고

이번 회차에서는 Next.js 미들웨어를 활용하여 요청 처리 전 로직을 추가하는 방법을 학습했습니다. 다음 20회차에서는 데이터 페칭 (Data Fetching) 전략 심화를 주제로, Next.js에서 데이터를 가져오는 다양한 방법(getServerSideProps, getStaticProps, getStaticPaths)을 심층적으로 다루고, 각 전략의 장단점 및 적절한 사용 시점을 비교 분석할 예정입니다. 미들웨어를 통해 요청을 제어하는 방법을 익혔으니, 이제는 효율적으로 데이터를 가져와 페이지에 표시하는 방법을 학습할 차례입니다.

✅ 이번 회차 핵심 정리
  • Next.js 미들웨어는 요청이 페이지/API 라우트에 도달하기 전에 실행되는 서버 사이드 코드입니다.
  • 주요 기능으로는 인증, 권한 부여, 리디렉션, 로깅, 헤더 조작 등이 있습니다.
  • 미들웨어는 프로젝트 루트에 middleware.ts 파일을 생성하여 정의하며, NextRequest를 받아 NextResponse를 반환합니다.
  • config.matcher를 사용하여 미들웨어가 실행될 특정 경로를 지정하여 성능을 최적화할 수 있습니다.
  • 무한 리디렉션 루프와 같은 오류를 방지하기 위해 미들웨어 로직과 matcher 설정을 신중하게 구성해야 합니다.

댓글 남기기

Wordpress Social Share Plugin powered by Ultimatelysocial
Copy link
URL has been copied successfully!
THREADS
RSS
error: 저작권 콘텐츠보호를 부탁드립니다.