☁️ goormTIL | Next.js #66

매루·2025년 12월 11일

goormTIL

목록 보기
64/67
post-thumbnail

📅 2025-12-11

➡️ Middleware, NextRequest/NextResponse, Auth, Cookie에 대해 새롭게 알게 된 것 또는 헷갈리는 부분 정리


🔎 학습 리마인드

📌 미들웨어 (Middleware)

  • 요청(Request)이 실제 페이지 또는 API Route에 도달하기 전에 먼저 실행되는 함수

  • 즉 라우팅 전에 선별 작업을 하는 입구 관리자 같은 역할

  • 예시

    • 권한 체크
      → 로그인 안 한 사용자가 /mypage 접근 시 /sign-in으로 보내기

    • 요청 차단
      → 특정 User-Agent, 특정 국가 IP 차단

    • 경로 재작성(Rewrite) / 리다이렉트(Redirect)
      /old/new


💡 규칙

  • 반드시 프로젝트 루트(app 폴더와 동일한 레벨)에 middleware.ts 생성
  • 프로젝트에 1개만 존재

💡 기본 형태

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

// This function can be marked `async` if using `await` inside
export function middleware(request: NextRequest) {
  return NextResponse.redirect(new URL('/home', request.url));
}

export const config = {
  matcher: '/about/:path*',
};

📌 NextRequest와 NextResponse

  • middleware에서 사용하는 두 가지 핵심 객체

    객체역할
    NextRequest들어온 요청 정보를 읽는 객체
    NextResponse요청을 어떻게 처리할지 결정하는 응답 객체
  • 둘 다 표준 Web Request/Response 기반이지만, Next.js에서 미들웨어용으로 기능이 확장된 버전


💡 NextResponse

  • 미들웨어에서 요청 흐름을 제어할 때 사용하는 객체 (redirect, rewrite, next 등 라우팅 제어 기능 포함)

NextResponse 4가지 패턴

// 1) 정상 진행
return NextResponse.next();

// 2) 다른 URL로 리다이렉트
return NextResponse.redirect(new URL("/about", request.url));

// 3) URL은 그대로 두고, 다른 페이지 컴포넌트 실행 (rewrite)
return NextResponse.rewrite(new URL("/faq", request.url));

// 4) JSON 바로 응답 (주로 API 권한 체크에 사용)
return NextResponse.json({ message: "권한이 없습니다." });

예시

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

export function middleware(request: NextRequest) {
  if (request.nextUrl.pathname.startsWith('/about')) {
    return NextResponse.rewrite(new URL('/about-2', request.url));
  }

  if (request.nextUrl.pathname.startsWith('/dashboard')) {
    return NextResponse.rewrite(new URL('/dashboard/user', request.url));
  }
}

💡 NextRequest

  • 미들웨어의 첫 번째 인자로 들어오는 요청 객체

주요 특징

  • request.nextUrl → pathname, params, searchParams 쉽게 가져오기
  • request.cookies → 쿠키 읽기
  • (환경 지원 시) geo 정보, ip 정보 확인

예시

export function middleware(request: NextRequest) {
  const { pathname, searchParams } = request.nextUrl;

  const token = request.cookies.get("token")?.value;
  const lang = request.cookies.get("lang")?.value ?? "ko";

  console.log("요청 경로:", pathname);
  console.log("쿼리:", searchParams.toString());
  console.log("토큰:", token);
  console.log("언어 설정:", lang);

  return NextResponse.next();
}

💡 Web Request/Response와의 차이

구분설명
표준 Request브라우저 fetch, Web API에서 쓰는 기본 요청 객체
URL/헤더/body 조회용
읽기 전용
NextRequestNext.js 확장 요청 객체
nextUrl, cookies, IP 등 프레임워크 친화적 기능 추가
주로 middleware와 route handler에서 사용
표준 Responsenew Response()로 생성하는 기본 응답 객체
상태코드/헤더/body만 제공
라우팅 제어 없음
NextResponseNext.js 확장 응답 객체
next/redirect/rewrite/json, 쿠키 설정 등 라우팅 + 응답 제어에 특화

→ 즉, 미들웨어에선 사실상 NextRequest / NextResponse가 표준처럼 쓰임


💡 matcher

  • 미들웨어가 어떤 경로에서만 실행될지 지정하는 옵션

  • 단일 경로

    export const config = {
      matcher: '/about/:path*',
    };
  • 여러 경로
    export const config = {
      matcher: ['/about/:path*', '/dashboard/:path*'],
    };
  • 정규표현식 (전체에서 일부 제외)
    export const config = {
      matcher: [
        /*
         * Match all request paths except for the ones starting with:
         * - api (API routes)
         * - _next/static (static files)
         * - _next/image (image optimization files)
         * - favicon.ico (favicon file)
         */
        '/((?!api|_next/static|_next/image|favicon.ico).*)',
      ],
    };
    • API Routes, 정적 파일, favicon.ico 제외하고 모든 라우트에서 미들웨어 실행

❗ matcher는 빌드 타임에 결정됨
→ 따라서 동적 값 사용 불가, 동적 매칭은 무시됨


📌 Authentication

개념의미
Authentication (인증)사용자가 누구인지 확인하는 과정
Authorization (인가)인증된 사용자가 해당 기능/페이지에 접근할 권한이 있는지 검증

💡 인증 종류

  1. OAuth / OpenID Connect (OIDC)

    • Google, Github 같은 외부 서비스 로그인
    • 비밀번호를 애플리케이션에 노출시키지 않음
    • SNS 로그인, 기업용 Single Sign-On(SSO) 등에 활용
  2. 사용자 자격 증명 기반 로그인

    • 이메일 + 비밀번호 입력
    • 전통적인 로그인 방식
    • 사설 서비스, 관리자 시스템에서 흔함
  3. 비밀번호 없는 인증 (토큰 기반)

    • 비밀번호 없이 토큰 기반 인증
    • 예: 이메일 Magic Link, SMS One-time Code
    • 모바일 / 간편 로그인에서 자주 사용

💡 인증 서버 json-server-auth

  • Next.js 실습에서 자주 사용하는 간단한 인증 서버
  • json-server 위에 인증 기능을 추가한 버전

설치 및 설정

  • 설치
    npm i json-server-auth -D
  • auth-db.json 작성
    {
      "users": [
        {
    	    "email": "test@test.com",
          "password": "$2a$10$eHUzzsY1tbOmyGVqPwVEsuYRQb4LP2Hw/dMXgxp5p8eYE4UgcZMA.", // password
          "id": 1
        }
      ]
    }
  • 실행 스크립트 추가 (package.json)
    "auth": "json-server-auth --watch auth-db.json --port 8000"
    • npm run auth로 실행

  • 웹사이트가 사용자 정보를 저장·유지하기 위해 사용하는 작은 데이터

쿠키의 대표적 사용

  • 인증 상태 저장 (accessToken, refreshToken 등)
  • 유저 설정 저장 (언어, 테마)
  • 세션 유지

💡 Next.js에서 쿠키 기본 개념

위치읽기쓰기
서버 컴포넌트 / 라우트 핸들러cookies()response.cookies.set()
미들웨어request.cookiesNextResponse().cookies.set()

💡 라우트 핸들러에서 쿠키 읽기

// app/api/me/route.ts
import { cookies } from "next/headers";
import { NextResponse } from "next/server";

export async function GET() {
  const cookieStore = cookies();

  const accessToken = cookieStore.get("accessToken"); // 없으면 undefined

  console.log("accessToken:", accessToken);

  return NextResponse.json({
    loggedIn: !!accessToken,
  });
}
  • cookies().get(name) → 없으면 undefined
  • 서버 환경에서만 동작하는 안전한 쿠키 읽기 방식

💡 라우트 핸들러에서 쿠키 쓰기

// app/api/auth/login/route.ts
import { NextResponse } from "next/server";

export async function POST(request: Request) {
  const payload = await request.json();

  const res = await fetch("http://localhost:8000/signin", {
    method: "POST",
    headers: { "Content-Type": "application/json" },
    body: JSON.stringify(payload),
  });

  const data = await res.json();
  // data.accessToken 이 있다고 가정

  const response = NextResponse.json({ success: true });

  response.cookies.set("accessToken", data.accessToken, {
    httpOnly: true,
    path: "/",
    // 선택 옵션들:
    // maxAge: 60 * 60 * 24, // 1일 (초 단위)
    // secure: true,         // https에서만 전송
    // sameSite: "lax",      // CSRF 방지에 도움
  });

  return response;
}

자주 쓰는 쿠키 옵션

옵션설명
httpOnlyJS로 읽기 불가능 → XSS 방지
secureHTTPS에서만 전송
path해당 경로 요청 시에만 쿠키 전송. 전체 적용은 "/"
maxAge쿠키 유지 시간(초 단위)
sameSiteCSRF 공격 방지에 도움

💡 미들웨어에서 쿠키 쓰기

import type { NextRequest } from "next/server";
import { NextResponse } from "next/server";

export function middleware(request: NextRequest) {
  const res = NextResponse.next();

  // 예: 처음 방문한 시간 기록
  if (!request.cookies.get("firstVisitAt")) {
    res.cookies.set("firstVisitAt", new Date().toISOString(), {
      httpOnly: true,
     });
  }

  return res;
}

export const config = {
  matcher: "/:path*",
};
  • 로그인 여부에 따라 특정 페이지 접근을 막는 예시
  • request.cookies → 읽기 전용
  • 쿠키를 쓰려면 반드시 NextResponse 객체에 set

0개의 댓글