1. 서버 컴포넌트, 클라이언트 컴포넌트, 미들웨어, 라우트 핸들러에서 쿠키 조작


쿠키에 접근할 때 서버 컴포넌트, 클라이언트 컴포넌트 미들웨어, 라우트 핸들러 등 각각의 시점에 따라 방법이 달라집니다.

<aside>

  1. 서버 컴포넌트

  2. 클라이언트 컴포넌트

  3. 미들웨어, 라우트 핸들러

2. 구현 - 서버 컴포넌트, 클라이언트 컴포넌트


서버 컴포넌트와 클라이언트 컴포넌트 쿠키 접근 로직은 서버 액션을 사용합니다. next/headerscookies()를 가져와 쿠키에 접근합니다. 일관적인 쿠키 정책을 적용하고 사용하기 쉬운 형태로 래핑합니다.

import { cookies } from 'next/headers';

function setCookie(name: string, value: string, options?: CookieOptions) {
  const securedOptions: CookieOptions = {
    ...SECURED_OPTIONS,
    ...options
  };
  cookies().set(name, value, securedOptions);
}

async function getCookie(name: string) {
  return cookies().get(name)?.value || '';
}

async function deleteCookie(name: string) {
  cookies().delete(name);
}

export async function handleSetCookie(name: string, value: string, options?: CookieOptions) {
  await setCookie(name, value, options);
}

export async function handleGetCookie(name: string) {
  return await getCookie(name);
}

export async function handleDeleteCookie(name: string) {
  await deleteCookie(name);
}

// 사용자 인증 토큰 쿠키 수정 로직
export function setNextCookieAuthToken(
  res: ResponseGenericBody<ResponseWrapper<PostLoginResponse | GetReissueTokenResponse>>,
  isAutoLogin: string | boolean,
  nextResponse: NextResponse<unknown>
) {
  const authorizationToken = res.headers.get(HEADERS.AUTHORIZATION_TOKEN) || '';
  const refreshToken = res.headers.get(HEADERS.REFRESH_TOKEN) || '';

  if (isAutoLogin) {
    nextResponse.cookies.set(HEADERS.AUTO_LOGIN, 'true', { ...SECURED_OPTIONS, ...OPTIONS });
    nextResponse.cookies.set(HEADERS.AUTHORIZATION_TOKEN, authorizationToken, { ...SECURED_OPTIONS, ...OPTIONS });
    if (refreshToken) nextResponse.cookies.set(HEADERS.REFRESH_TOKEN, refreshToken, { ...SECURED_OPTIONS, ...OPTIONS });
    return;
  }

  nextResponse.cookies.delete(HEADERS.AUTO_LOGIN);
  nextResponse.cookies.set(HEADERS.AUTHORIZATION_TOKEN, authorizationToken, SECURED_OPTIONS);
  if (refreshToken) nextResponse.cookies.set(HEADERS.REFRESH_TOKEN, refreshToken, SECURED_OPTIONS);
}

export function deleteNextCookieAuthToken(nextResponse: NextResponse<unknown>) {
  nextResponse.cookies.delete(HEADERS.AUTO_LOGIN);
  nextResponse.cookies.delete(HEADERS.AUTHORIZATION_TOKEN);
  nextResponse.cookies.delete(HEADERS.REFRESH_TOKEN);
}

서버 컴포넌트에서는 cookies() 함수를 직접 가져와 사용할 수 있습니다. 서버 액션을 통해 함수를 호출할 경우 불필요한 계층이 생깁니다. 하지만 1) 쿠키 접근 로직을 중앙화할 수 있고, 2) 일관적인 쿠키 정책을 적용할 수 있으며, 3) 앞선 두 이점이 가져오는 이득에 비해 증가한 함수 호출 계층이 가져오는 성능 하락이 무시할 수 있는 수준이기 때문에 서버, 클라이언트 측 모두 서버 액션을 통해 쿠키에 접근합니다.

❗서버 컴포넌트에서 쿠키 쓰기 작업 시 문제점

클라이언트 컴포넌트가 로드되기 전 서버 컴포넌트에서 쿠키를 수정할 경우, 클라이언트가 로드된 뒤 전후 데이터가 불일치할 경우 하이드레이션 오류를 발생시킬 수 있습니다. 따라서 서버 컴포넌트에서는 간단한 쿠키 읽기 작업을 수행하고, 복잡한 비즈니스 로직이나 쿠키 쓰기 작업이 필요할 경우 Request-Response 생명주기 내부에서 실행하는 것이 권장됩니다.

3. 구현 - 미들웨어, 라우트 핸들러


미들웨어와 라우트 핸들러는 Request-Response 생명주기 내부에 위치하기 때문에 직접 헤더에 접근할 수 있습니다. next/serverNextResponse.cookies를 통해 쿠키를 읽고 수정합니다.

import { NextResponse } from 'next/server';

// 사용자 인증 토큰 쿠키 수정 로직
export function setNextCookieAuthToken(
  res: ResponseGenericBody<ResponseWrapper<PostLoginResponse | GetReissueTokenResponse>>,
  isAutoLogin: string | boolean,
  nextResponse: NextResponse<unknown>
) {
  const authorizationToken = res.headers.get(HEADERS.AUTHORIZATION_TOKEN) || '';
  const refreshToken = res.headers.get(HEADERS.REFRESH_TOKEN) || '';

  if (isAutoLogin) {
    nextResponse.cookies.set(HEADERS.AUTO_LOGIN, 'true', { ...SECURED_OPTIONS, ...OPTIONS });
    nextResponse.cookies.set(HEADERS.AUTHORIZATION_TOKEN, authorizationToken, { ...SECURED_OPTIONS, ...OPTIONS });
    if (refreshToken) nextResponse.cookies.set(HEADERS.REFRESH_TOKEN, refreshToken, { ...SECURED_OPTIONS, ...OPTIONS });
    return;
  }

  nextResponse.cookies.delete(HEADERS.AUTO_LOGIN);
  nextResponse.cookies.set(HEADERS.AUTHORIZATION_TOKEN, authorizationToken, SECURED_OPTIONS);
  if (refreshToken) nextResponse.cookies.set(HEADERS.REFRESH_TOKEN, refreshToken, SECURED_OPTIONS);
}

export function deleteNextCookieAuthToken(nextResponse: NextResponse<unknown>) {
  nextResponse.cookies.delete(HEADERS.AUTO_LOGIN);
  nextResponse.cookies.delete(HEADERS.AUTHORIZATION_TOKEN);
  nextResponse.cookies.delete(HEADERS.REFRESH_TOKEN);
}