Next.js 로컬 환경에서 쿠키의 도메인 불일치 해결하기

Jay·2025년 2월 20일
0
post-thumbnail

React로 구현된 기존 프로젝트를 Next.js로 마이그레이션하는 과정에서, 실제 배포된 백엔드에서 전달하는 Cookie가 로컬 환경의 Next.js 서버로 정상적으로 전달되지 않는 이슈를 겪었습니다.

이 문제를 해결하기 위해 다양한 시도를 거쳤으며, 최종적으로 적용한 해결법과 그 과정에서의 삽질을 정리해 보려고 합니다.

저의 삽질 과정이 아닌 문제 해결 방법 내용만 보고 싶은 분은 새로운 해결 방법 섹터 내용만 보셔도 좋습니다.

문제

로그인 성공 시, 백엔드 서버에서 쿠키가 설정됩니다.

클라이언트에서 백엔드(release.example.co)로 요청을 보낼 때는 해당 쿠키가 잘 포함되어 전송됩니다.

문제는 Next.js 서버에서 백엔드로 요청을 보낼 때 발생합니다.

Next.js 서버가 백엔드에서 설정한 쿠키를 사용하려면, 클라이언트에서 해당 쿠키를 Next.js 서버로 전달해야 합니다. 하지만, 쿠키의 도메인이 .example.co로 설정되어 있기 때문에, 도메인이 localhost:3000인 Next.js 서버에는 쿠키가 전달되지 않습니다.

이 문제는 배포가 진행된다면, next.eaxmple.co와 같은 도메인을 가지게 될 예정이기 때문에 개발 환경에서만 발생하는 문제입니다.

목표

배포되지 않은 로컬 개발 환경에서 배포된 백엔드 서버가 제공하는 쿠키를 사용해서 SSR을 수행할 수 있는 상태를 만들기

접근 방식

이 문제는 서버로부터 받는 쿠키의 도메인이 .example.co이고, 개발 환경의 도메인은 localhost:3000이기 때문입니다.
그렇다면 쿠키의 도메인을 localhost로 변경하면 될 것 같다는 생각이 들었습니다.

쿠키는 httponly로 설정되어 있지만, Server에서는 쿠키의 값을 변경할 수 있다는 점을 이용하기로 했습니다.
이를 수행하기 위해서 프로젝트의 모든 API를 Next.js API 라우트를 마치 프록시처럼 거쳐가도록 했습니다.

예를 들어, 클라이언트에서 /api/login 경로를 호출하면, 이를 Next.js 서버에서 /api/proxy/login으로 받아 백엔드로 /api/login 요청을 전달합니다.

이 과정에서 API 라우트는 쿠키의 도메인 .example.colocalhost로 변경하여 설정합니다.

두 번째 문제

저희 서버는 쿠키의 엑세스 토큰이 만료되었지만, 리프레쉬 토큰이 아직 만료되지 않았다면, 어떤 네트워크 요청에도 이를 확인 후 새로 쿠키를 설정해줍니다.

그렇다는건 어떤 요청에서 새로운 쿠키가 올지 모르니, 모든 API 요청에 새로 설정된 쿠키를 .example.colocalhost로 변경하는 로직이 필요하게됩니다.

프로젝트의 API는 한 두개가 아니고 추가, 삭제, 변경에 대한 대응 또한 필요합니다.

자동화로 해결

모든 API의 추가 변경에 대응하기 위해서 자동화 시스템을 만들기로 했습니다.

우선 저희 프로젝트에서는 다음과 같은 컨벤션을 따르고 있습니다.

API 정의는 프로젝트 내의 특정 경로인/src/features/{featureName}/api/index.ts에 위치하며, 각 파일에서는 프로젝트에서 구현한 커스텀 fetcher를 활용하여 API 메서드가 정의되어 있습니다.

그리고 index.ts는 다음과 같은 형태를 가지고 있습니다.

// /src/features/auth/api/index.ts 

export const postSignIn = async (body: SignInCredentials) => {
  const res = await fetcher.post<Response<SignInData>>("/auth/login", body);
  return res.data;
};

export const postSignOut = async () => {
  const res = await fetcher.post<Response<null>>("/auth/logout");
  return res.data;
};

// ... 일부 생략

이 컨벤션이 프로젝트 전반에 걸쳐 일관되게 유지되고 있으며 앞으로도 지켜질 수 있다면, 이를 기반으로 Code Generator를 구현하여 문제를 해결할 수 있다고 판단했습니다.

이를 위해 chokidar 라이브러리를 활용하여 /src/features/{featureName}/api/index.ts 파일의 변경을 실시간으로 감지하도록 설정했습니다.

그 다음, 변경된 index.ts 파일을 파싱하여 각 API에 매핑되는 API 라우트를 자동으로 생성하는 Code Generator를 구현했습니다.

Code generator

Code generator는 chokidar를 활용하여 개발 환경 실행 시 함께 실행되며, 아래와 같은 작업을 수행합니다

  1. API 파일 변경 감지

    /src/features/{featureName}/api/index.ts 파일의 변경을 실시간으로 감지합니다.

  2. 초기화 작업

    개발 환경이 실행되기 전에 /src/features/{featureName}/api/index.ts에 변경이 있었을 가능성을 대비하여, 실행 시 모든 API 파일을 순회하며 API 라우트를 초기화합니다. 이를 통해 누락된 라우트가 없도록 보장합니다.

  3. API 추가/수정 반영

    index.ts 파일에서 API가 추가되거나 수정된 경우, 해당 파일을 읽어 API 정의를 파싱한 뒤, 이에 매핑되는 API 라우트를 생성하거나 수정합니다.

  4. API 삭제 처리

    index.ts 파일에서 API가 삭제된 경우, 어떤 API 라우트가 삭제되어야 하는지 정확히 특정할 수 없기 때문에, 해당 API 파일을 다시 순회하며 초기화 작업을 실행합니다. 이를 통해 삭제된 API 라우트도 반영되도록 합니다.

자세한 Code Generator의 코드가 궁금하시면 링크를 확인해 주세요.
https://github.com/fine-ants/frontend-next/pull/17/files#diff-aa228003efd97291a7e25f6e542a0f2cd42b169bab571e557590a73906f4a28b
https://github.com/fine-ants/frontend-next/pull/21/files#diff-56a513d17983b597cf8d966d9980a8af9a29a4928ee3f9ae45caa7bb84341444

Code Generator - 결과물

Code Generator를 통해 다음과 같은 API 라우트들이 생성됩니다.

생성된 파일은 다음과 같습니다.
백엔드 서버로부터 받은 cookie의 도메인을 localhost로 변경하여 그대로 제공하기만 합니다.

// /src/pages/api/prox/auth/login.ts

// 일부 생략
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
  const cookies = req.cookies;
  const cookieString = Object.entries(cookies)
    .map(([key, value]) => `${key}=${value}`)
    .join("; ");

  

  switch (req.method) {
      case "POST":
        try {
          const { data, cookies: { accessToken, refreshToken } = {} } =
            await proxyFetcher.post<Response<unknown>>(`/auth/login`, req.body, {
              headers: {
                Cookie: cookieString || "",
              },
            });

          if (accessToken && refreshToken) {
            const updatedAccessToken = replaceCookieDomain(
              accessToken,
              "localhost"
            );
            const updatedRefreshToken = replaceCookieDomain(
              refreshToken,
              "localhost"
            );

            res.setHeader("Set-Cookie", [updatedAccessToken, updatedRefreshToken]);
          }

          return res.status(200).json(data);
        } catch (error) {
          return res.status(500).json({ error: "Internal server error" });
        }
      
    default:
      res.setHeader("Allow", ["GET", "POST"]);
      res.status(405).end(`Method ${req.method} Not Allowed`);
  }
}

// 일부 생략

단순하게 index.ts 파일에 선언된 API들을 파싱하여 각각의 호출 url메서드를 기반으로 분리하고 url에 맞는 경로와 메서드를 가지는 API 라우트를 만들게됩니다.

정리

프론트엔드 로컬 환경과 배포된 백엔드를 연결하여 개발을 진행하는 과정에서, 백엔드에서 설정한 쿠키의 도메인 문제로 인해 Next.js 서버에서 쿠키를 사용할 수 없는 문제가 있었습니다.

이를 해결하기 위해 Next.js에서 프록시와 유사한 환경을 구성하여, Next.js 서버를 통해 쿠키를 새로 설정하고 모든 API 요청이 Next.js 서버를 통해 처리되도록 환경을 수정했습니다.

이와 같은 환경에 맞추어 모든 API 요청에 매핑되는 API 라우트를 생성해야 했기 때문에, Code Generator를 구현했습니다. 이 Code Generator는 API 파일이 선언된 경로 /src/features/{featureName}/api/index.ts의 변경을 감시하여, 해당 API에 맞는 API 라우트를 자동으로 생성하도록 설계되었습니다.

배포 환경에서는 기본 API 요청 방식과 동일하게 동작하기 때문에, 프록시 경로는 .gitignore에 추가하여 GitHub에 배포되지 않도록 처리했습니다. 이를 통해 개발 환경과 배포 환경 간의 충돌 없이 효율적으로 운영할 수 있는 구조를 완성했습니다.

새로운 배움

이 문제를 해결하기 위해 먼저 주변 지인과 부트캠프 커뮤니티에 질문했지만, 마땅한 해결책을 찾지 못해 스스로 방법을 고민하며 해결했습니다.

최근 면접에서 이 문제 해결 경험을 이야기하던 중, 면접관께서 좋은 경험이라고 평가해 주셨고, 더 쉬운 해결 방법을 제안해 주셨습니다. 해당 방법이 더 효율적이라고 생각되어 실제로 적용해 보았습니다.

면접은 불합격이지만 감사합니다.

새로운 해결 방법

해결 방식은 hosts 파일을 수정하면 로컬 도메인을 변경하는 방식입니다.

즉, localhost:3000 대신 local.example.co와 같은 도메인으로 지정할 수 있다는 의미입니다. 이 방법으로 기존처럼 API 라우트를 Proxy처럼 사용하는 복잡한 과정이 필요하지 않았습니다.

운영체제별 hosts 파일 경로는 다음과 같습니다.

  • Windows: C:\Windows\System32\drivers\etc\hosts
  • Linux/Mac: /etc/hosts

이 파일을 관리자 권한으로 열어 아래와 같이 추가합니다.

127.0.0.1 local.example.co

이제 로컬 프로젝트를 local.example.co로 실행하면 쿠키를 정상적으로 접근할 수 있습니다.

Next.js에서 제공하는 -H 옵션을 사용하여 원하는 도메인을 설정할 수 있습니다.

"scripts": {
    "dev": "next dev -H local.example.co"
}

결론

부트캠프 커뮤니티, 주변 지인, 스택오버플로우 등 여러 곳에 질문해 보고 인터넷 검색도 해봤지만, 이 문제를 해결할 방법을 찾지 못했습니다.

처음에는 어떻게 쿠키를 로컬 Next.js 서버로 보낼까? 라는 관점에서 접근했지만, 더 근본적인 원인인 쿠키 도메인 불일치 해결 에는 집중하지 못했던 것 같습니다.

그럼에도 불구하고, 기존의 접근 방식과 방향성을 유지하면서 결국 저만의 방식으로 문제를 해결했습니다. 그렇기 때문에 이 방법은 다른 접근 방식일 뿐 틀렸다고 생각하지 않습니다.

때로는 정말 좋은 해결책이 없는 문제도 있기 마련이기에, 도전하고 해결하는 과정 자체가 의미 있다고 느낍니다.

이러한 경험이 언젠가는 반드시 도움이 될 것이고, 덕분에 더욱 성장할 수 있었다고 생각합니다. 개발자의 묘미는 결국 문제를 피하지 않고 해결 방법을 찾아가는 과정에 있다고 믿고, 이런 도전 자체가 정말 재미있었습니다.

profile
병아리 개발자

0개의 댓글