Next.js 외부 API 연동, 클라이언트 401 에러를 서버와 API Route로 해결한 과정

양정규·2025년 6월 28일
post-thumbnail

목차

  1. 문제: 왜 클라이언트에서 인증 API 호출이 실패할까?
  2. 첫 번째 시도: 클라이언트 컴포넌트에서 직접 호출 (실패)
  3. 두 번째 시도: 서버 컴포넌트에서 데이터 패칭 (성공)
  4. 세 번째 시도: API Route(프록시) + 클라이언트 컴포넌트 (성공)
  5. 최종 정리: Next.js에서 인증이 필요한 외부 API는 반드시 서버(API Route, 서버 컴포넌트)에서 호출하자!
  6. 회고

1. 문제: 왜 클라이언트에서 인증 API 호출이 실패할까?

Next.js 15에서 조직의 GitHub 레포지토리 목록을 가져오는 기능을 만들고 싶었습니다.
처음에는 단순히 클라이언트 컴포넌트에서 fetch로 GitHub API를 호출하면 될 거라 생각했습니다.

하지만,

  • 401 Unauthorized
  • CORS 에러
  • 환경변수 노출 불가

등의 문제에 부딪혔습니다.


2. 첫 번째 시도: 클라이언트 컴포넌트에서 직접 호출 (실패)

처음엔 아래처럼 작성했습니다.

// app/org-test/page.jsx
"use client";
import { useState } from "react";
import { getOrgRepos } from "../data"; // 서버에서 인증 토큰을 붙여 fetch하는 함수

export default function OrgTestPage() {
  const [orgName, setOrgName] = useState("");
  const [repos, setRepos] = useState([]);

  // ❌ 브라우저에서 직접 서버 함수 호출
  const handleFetch = async () => {
    const data = await getOrgRepos(orgName);
    setRepos(data);
  };

  return (
    <div>
      <input value={orgName} onChange={e => setOrgName(e.target.value)} />
      <button onClick={handleFetch}>레포 가져오기</button>
      <ul>
        {repos.map(repo => <li key={repo.id}>{repo.name}</li>)}
      </ul>
    </div>
  );
}

// app/data.js
export async function getOrgRepos(orgName) {
  const res = await fetch(
    `https://api.github.com/orgs/${orgName}/repos?per_page=100`,
    {
      headers: { Authorization: `Bearer ${process.env.GH_TOKEN}` }, // 환경변수 사용
    }
  );
  if (!res.ok) {
    // 에러 처리
    return [];
  }
  return res.json();
}

문제점

  • 브라우저에서는 환경변수(GH_TOKEN)에 접근할 수 없음
  • 인증 헤더가 누락되어 401 에러 발생
  • CORS 정책에 막힘

3. 두 번째 시도: 서버 컴포넌트에서 데이터 패칭 (성공)

Next.js의 서버 컴포넌트는 서버에서만 실행되므로, 환경변수와 인증 토큰을 안전하게 사용할 수 있습니다.

// app/org-test/page.jsx
import { getOrgRepos } from "../data";

export default async function OrgTestPage() {
  const orgName = "preOnBorading-Idle";
  // ✅ 서버에서 환경변수(GH_TOKEN)로 안전하게 fetch
  const repos = await getOrgRepos(orgName);

  return (
    <div>
      <h1>조직명: {orgName}</h1>
      <ul>
        {repos.map(repo => <li key={repo.id}>{repo.name}</li>)}
      </ul>
    </div>
  );
}

결과

  • 서버에서 안전하게 인증 토큰을 사용해 GitHub API 호출 성공
  • 브라우저에는 토큰이 노출되지 않음

4. 세 번째 시도: API Route(프록시) + 클라이언트 컴포넌트 (성공)

클라이언트 컴포넌트에서 직접 외부 API를 호출하는 대신,
내 서버의 API Route(프록시)를 통해 데이터를 받아오는 구조로 변경했습니다.

4-1. API Route 생성 (app/api/org-repos/route.js)

// app/api/org-repos/route.js
export async function GET(request) {
  const { searchParams } = new URL(request.url);
  const org = searchParams.get("org");
  const response = await fetch(
    `https://api.github.com/orgs/${org}/repos?per_page=100`,
    {
      headers: { Authorization: `Bearer ${process.env.GH_TOKEN}` },
    }
  );
  const data = await response.json();
  return Response.json(data, { status: response.status });
}

4-2. 클라이언트 컴포넌트에서 API Route로 요청

// app/org-test/page.jsx
"use client";
import { useState } from "react";

export default function OrgTestPage() {
  const [orgName, setOrgName] = useState("");
  const [repos, setRepos] = useState([]);

  // ✅ 내 서버의 API Route로만 요청
  const handleFetch = async () => {
    const res = await fetch(`/api/org-repos?org=${encodeURIComponent(orgName)}`);
    const data = await res.json();
    setRepos(data);
  };

  return (
    <div>
      <input value={orgName} onChange={e => setOrgName(e.target.value)} />
      <button onClick={handleFetch}>레포 가져오기</button>
      <ul>
        {repos.map(repo => <li key={repo.id}>{repo.name}</li>)}
      </ul>
    </div>
  );
}

결과

  • 브라우저에서는 내 서버의 API Route(/api/org-repos)로만 요청
  • 서버(API Route)에서 환경변수/인증 토큰을 사용해 GitHub API에 안전하게 요청
  • 브라우저에는 인증 정보가 노출되지 않으면서, 정상적으로 데이터가 출력됨

5. ✅ 최종 정리: Next.js에서 인증이 필요한 외부 API는 반드시 서버(API Route, 서버 컴포넌트)에서 호출하자!

  • 브라우저(클라이언트 컴포넌트)에서는 외부 API에 직접 요청하지 않는다.
    • 인증 정보 노출, 401 에러, CORS 문제 등 다양한 위험이 있다.
  • API Route(프록시) 또는 서버 컴포넌트에서만 외부 API와 통신한다.
    • 환경변수(토큰)를 안전하게 사용할 수 있고, 보안과 CORS 문제를 모두 해결할 수 있다.
  • 클라이언트 컴포넌트에서는 내 서버의 API Route만 호출한다.
    • 서버가 외부 API와 통신한 결과만 받아서 화면에 렌더링한다.
  • Next.js 15의 app/api 구조를 활용하면, 별도의 백엔드 없이도 안전하고 효율적으로 API 연동이 가능하다.

6. 회고

동적 입력/상호작용이 필요하면 프록시(API Route),
정적 데이터는 서버 컴포넌트에서 직접 패칭!

처음엔 클라이언트에서 바로 외부 API를 호출하려다 401 에러에 막혀서 당황했지만, 이번 경험을 통해서 동적 입력이 필요할 땐 프록시(API Route)를 사용하고 정적 데이터는 서버 컴포넌트를 써야 한다는 걸 이번에 확실히 배웠다.

profile
롤보다 개발이 재밌는 프론트엔드 개발자입니다 :D

0개의 댓글