
📅 2025-12-05
➡️ Next.js Router Handler, Server Action에 대해 새롭게 알게 된 것 또는 헷갈리는 부분 정리
app 디렉토리 안에서만 작동 
app/api/newBooks/route.ts → GET /api/newBooksexport async function GET(request: Request) {}
export async function POST(request: Request) {}
export async function PUT(request: Request) {}
export async function DELETE(request: Request) {}// app/api/books/route.ts
import { Book } from "@/types/book";
export async function GET() {
const res = await fetch("http://localhost:4000/books");
const data: Book[] = await res.json();
return Response.json({ data: data });
}
http://localhost:3000/api/books 로 요청 시 정상 응답 (Postman 등 API 테스트 도구 사용 가능)API 요청 시 민감 정보(API KEY, Token 등) 숨기고 싶을 때
API 요청 시 CORS 문제 우회하고 싶을 때
외부 서비스나 앱에서도 사용할 수 있는 REST API 제공하고 싶을 때
Same-Origin Policy (동일 출처 정책)
mywebsite.com → mywebsite.com/api ✅ (가능) mywebsite.com → another-website.com/api ❌ (CORS 에러)CORS (Cross-Origin Resource Sharing)
Access-Control-Allow-Origin 헤더를 통해 허용 도메인 지정❓ CORS 에러란?
서버가 허용하지 않은 출처에서 요청이 들어온 경우, 브라우저가 보안 정책으로 응답을 차단하는 현상
- 즉, 브라우저가 보안상 적용하는 규칙
동작 과정
- 클라이언트에서 API 요청 (
fetch,axios등)- 브라우저가 CORS 정책 검사
- 서버 응답에서
Access-Control-Allow-Origin헤더 확인- 허용 시 응답 처리 / 아니면 차단
Simple Request(일반 요청) vs Preflight Request(사전 요청)
CORS 요청은 일반요청과, 사전요청으로 나뉨
| 종류 | 설명 |
|---|---|
| Simple Request | 바로 요청 가능 (일반 GET / POST) |
| Preflight Request (OPTIONS) | PUT / DELETE / PATCH 또는 커스텀 헤더 포함 시 사전 확인 요청 필요 |
정상 요청 흐름
fetch 실행)200 OK + Access-Control-Allow-Origin 포함)// Next.js API Route (app/api/proxy/route.ts)
import { NextResponse } from "next/server";
export async function GET() {
const response = await fetch("https://api.example.com/data"); // 실제 API 요청
const data = await response.json();
return NextResponse.json(data, {
headers: {
"Access-Control-Allow-Origin": "*", // 모든 도메인에서 접근 가능
},
});
}revalidatePath, revalidateTag 를 통해 자동 데이터 갱신 지원revalidateTag
특정 데이터 그룹만 골라서 무효화
revalidateTag("books");
"books" 태그가 붙은 모든 fetch 캐시 무효화TanStack Query 비교 → invalidateQueries(key)와 유사
queryClient.invalidateQueries({ queryKey: ["books"] });
["books"] key로 캐싱된 모든 데이터를 무효화revalidatePath
특정 페이지 전체에 대한 캐시 무효화
revalidatePath("/dashboard");
/dashboard에서 사용된 모든 fetch 캐시 무효화TanStack Query 비교 → invalidateQueries 여러 개 + 페이지 전체 재검증
queryClient.invalidateQueries(["products"]);
queryClient.invalidateQueries(["user"]);
queryClient.invalidateQueries(["recommendations"]);
"use server" 선언// app/server-action.ts
"use server";
import { Book } from "@/types/book";
import { BASE_URL } from "./constants/api";
export async function getBooks() {
const res = await fetch(`${BASE_URL}/books`, {
cache: "no-store",
});
const data: Book[] = await res.json();
return { data };
} export default async function Home() {
//const res = await fetch("http://localhost:4000/books");
//const books: Book[] = await res.json();
const { data: books } = await getBooks();
return (
<div>
<h1>Books</h1>
<p>개발 서적 목록</p>
<BookList books={books} />
)
}import React from "react";
import { sayHello } from "./actions";
const Children = () => {
console.log("children hello");
return (
<div>
Children
<form action={sayHello}>
<button> say hello</button>
</form>
</div>
);
};
export default Children;| 항목 | Route Handler | Server Action |
|---|---|---|
| 목적 | REST API 구축 | React 컴포넌트에 밀접한 서버 로직 |
| 접근 방식 | URL로 호출 | 함수 호출처럼 사용 |
| 사용하는 곳 | FE/BE/모바일/외부 서비스 모두 사용 가능 | React 컴포넌트에서만 사용 |
| 보이는 HTTP 메서드 | GET / POST / PUT / DELETE | 항상 POST로 감춰짐 |
| 보안 | URL이 외부에 노출될 수 있음 | URL이 없음 → 더 안전 |
| 데이터 처리용 | 읽기/쓰기 모두 적합 | 쓰기(Create/Update/Delete)에 최적 |
| 캐싱 제어 | fetch 옵션으로 ISR 가능 | fetch 내부에서만 제어 가능 |