Next.js 15에서 조직의 GitHub 레포지토리 목록을 가져오는 기능을 만들고 싶었습니다.
처음에는 단순히 클라이언트 컴포넌트에서 fetch로 GitHub API를 호출하면 될 거라 생각했습니다.
하지만,
등의 문제에 부딪혔습니다.
처음엔 아래처럼 작성했습니다.
// 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();
}
문제점
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>
);
}
결과
클라이언트 컴포넌트에서 직접 외부 API를 호출하는 대신,
내 서버의 API Route(프록시)를 통해 데이터를 받아오는 구조로 변경했습니다.
// 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 });
}
// 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/org-repos)로만 요청동적 입력/상호작용이 필요하면 프록시(API Route),
정적 데이터는 서버 컴포넌트에서 직접 패칭!
처음엔 클라이언트에서 바로 외부 API를 호출하려다 401 에러에 막혀서 당황했지만, 이번 경험을 통해서 동적 입력이 필요할 땐 프록시(API Route)를 사용하고 정적 데이터는 서버 컴포넌트를 써야 한다는 걸 이번에 확실히 배웠다.