Next.js ์๋ฒ ์ก์ ๊ณผ ํด๋ผ์ด์ธํธ Fetch๋ฅผ ํผํฉํ ๋ ๋ฐ์ํ๋ ์บ์ฑ ๋ฌธ์ ํด๊ฒฐ๋ฒ
Next.js 13 ์ดํ App Router๊ฐ ๋์ ๋๊ณ , 14์์ Server Actions๊ฐ ๊ณต์ ๊ธฐ๋ฅ์ผ๋ก ๊ฐํ๋๋ฉด์ ์๋ฒ ์ค์ฌ ์ํคํ ์ฒ๋ฅผ ์ ์ฉํ๋ ํ๋ก์ ํธ๊ฐ ์ฆ๊ฐํ๊ณ ์๋ค. ์ด ๊ณผ์ ์์ ๋ง์ ๊ฐ๋ฐ์๋ค์ด ๊ณตํต์ ์ผ๋ก ๊ฒช๋ ์ด๋ ค์์ด ์๋ค. ๋ฐ๋ก Server Actions์ ํด๋ผ์ด์ธํธ Fetch ์์ฒญ์ด ํจ๊ป ์ฐ์ผ ๋ ๋ฐ์ํ๋ ์บ์ฑ ๋ฌธ์ ๋ค.
Next.js๋ ๊ธฐ๋ณธ์ ์ผ๋ก "์๋ฒ ์ค์ฌ ๋ ๋๋ง๊ณผ ์บ์ฑ"์ ๊ฐ๋ ฅํ๊ฒ ๋ฐ์ด๋ถ์ด๋ ํ๋ ์์ํฌ๋ค. ๊ทธ๋ฌ๋ ์ด ๊ตฌ์กฐ๋ Server Actions์ fetch API๊ฐ ๋ค์์ธ ์ค์ ํ๊ฒฝ์์๋ ์๊ธฐ์น ๋ชปํ ๋ฒ๊ทธ๋ฅผ ๋ง๋ ๋ค. ์ด ๊ธ์์๋ ์ด๋ฌํ ๋ฌธ์ ๋ค์ด ์ ๋ฐ์ํ๋์ง, ๊ทธ๋ฆฌ๊ณ ์ด๋ฅผ ์ด๋ป๊ฒ ํด๊ฒฐํ ์ ์๋์ง ์ค์ ์ฝ๋ ๊ธฐ๋ฐ์ผ๋ก ์ ๋ฆฌํด๋ณธ๋ค.
1. ์ Server Actions์ Fetch๋ฅผ ํผ์ฉํ๋ฉด ๋ฌธ์ ๊ฐ ์๊ธฐ๋๊ฐ
Next.js์ fetch๋ ๊ธฐ๋ณธ์ ์ผ๋ก ์๋ฒ์์ ์คํ๋ ๋ ๋ค์๊ณผ ๊ฐ์ ํน์ง์ ๊ฐ์ง๋ค.
- ๋์ผํ fetch ์์ฒญ์ ๋์ผํ ์ ๋ ฅ์ ๋ํด ์๋ ์บ์ฑ๋๋ค.
- Response๋ ๋น๋ ์์ , ์ฌ์์ฒญ ์์ , ํน์ ๊ฒฝ๋ก์ ์บ์ฑ ์ ์ฑ ์ ๋ฐ๋ผ ๊ณต์ ๋๋ค.
- Server Component์์๋ fetch๊ฐ ์๋ฒ ์บ์๋ฅผ ์ ๊ทน์ ์ผ๋ก ํ์ฉํ๋ค.
๋ฐ๋ฉด Server Actions๋ ๋ค์ ํน์ฑ์ ๊ฐ์ง๋ค.
- ์คํ๋ ๋ ์๋์ผ๋ก revalidate๋ฅผ ํธ๋ฆฌ๊ฑฐํ์ง ์๋๋ค.
- Action ํธ์ถ ํ UI๊ฐ ์๋์ผ๋ก ๊ฐฑ์ ๋์ง ์๋๋ค.
- Server Component๊ฐ ์๋๋ผ Client Component์์ ํธ์ถ๋ ์ ์๋ค.
์ฆ, ์ด ๋์ด ์กฐํฉ๋๋ ์ํฉ์์ ๋ค์๊ณผ ๊ฐ์ ์ ํ์ ์ธ ๋ฌธ์ ๊ฐ ์ฝ๊ฒ ๋ฐ์ํ๋ค.
2. ์ ํ์ ์ธ ๋ฌธ์ ์ฌ๋ก
๋ฌธ์ 1. Server Action์ผ๋ก ๋ฐ์ดํฐ๋ฅผ ์์ ํ์ง๋ง UI๋ ๊ทธ๋๋ก์ธ ๊ฒฝ์ฐ
์์ ์ฝ๋:
// server action
export async function updateName(id: string, name: string) {
await prisma.user.update({ where: { id }, data: { name } });
}
๊ทธ๋ฆฌ๊ณ ์๋ฒ ์ปดํฌ๋ํธ์์ ๋ฐ์ดํฐ ํจ์นญ:
const user = await fetch("https://api.example.com/user/1").then(res => res.json());
๋ฌธ์ ๋ updateName ์คํ ํ์๋ fetch๊ฐ ์ด์ ์บ์๋ ๊ฐ์ ๊ทธ๋๋ก ๋ฐํํ๋ค๋ ์ ์ด๋ค.
์ฆ, DB ๋ณ๊ฒฝ์ ์๋ฃ๋์์ง๋ง Next.js๋ ์ด๋ฅผ ๋ชจ๋ฅด๋ ์ํ๋ค.
๋ฌธ์ 2. ํด๋ผ์ด์ธํธ Fetch๋ ์ต์ ๋ฐ์ดํฐ๋ฅผ ๊ฐ์ ธ์ค์ง๋ง ์๋ฒ ์ปดํฌ๋ํธ๋ ์บ์ฑ๋ ๋ฐ์ดํฐ๋ฅผ ์ ์ง
ํด๋ผ์ด์ธํธ์์ ๋ค์์ฒ๋ผ ์ฌ์์ฒญํ๋ฉด:
const res = await fetch("/api/user/1", { cache: "no-store" });
ํญ์ ์ต์ ๋ฐ์ดํฐ๋ฅผ ๊ฐ์ ธ์จ๋ค.
๊ทธ๋ฌ๋ ํ์ด์ง๋ ์๋ฒ ์ปดํฌ๋ํธ์์ ํจ์นญ๋ ์บ์๋ ๋ฐ์ดํฐ๋ฅผ ๊ทธ๋๋ก ๋ณด์ฌ์ค๋ค.
๋ฌธ์ 3. fetch๊ฐ ์๋ ์บ์ฑ๋๋ค๋ ์ ์ ์ธ์งํ์ง ๋ชปํ ์ํ์์ ๋ฐ์ํ๋ ๊ต์ฐจ ๋ฒ๊ทธ
App Router์ ๊ธฐ๋ณธ fetch ์ ์ฑ :
- ์๋ฒ์์ ์คํ: default = cache: โforce-cacheโ
- ํด๋ผ์ด์ธํธ Fetch: default = cache: โno-storeโ
์ฆ, ๊ฐ์ URL์ ํธ์ถํด๋ ์๋ฒ/ํด๋ผ์ด์ธํธ ๊ฒฐ๊ณผ๊ฐ ๋ฌ๋ผ์ง๋ค.
3. ํด๊ฒฐ ์ ๋ต ์ ๋ฆฌ
์ด ๋ฌธ์ ๋ ์ฌ๋ฌ ๋ฐฉ์์ผ๋ก ํด๊ฒฐํ ์ ์๋ค. ๋ํ์ ์ธ ์ ๋ต 4๊ฐ์ง๋ ๋ค์๊ณผ ๊ฐ๋ค.
1) fetch์์ ์บ์ฑ์ ๋ช ์์ ์ผ๋ก ์ ์ดํ๋ค
๊ฐ์ฅ ๊ฐ๋จํ ๋ฐฉ๋ฒ์ด๋ค.
์๋ฒ ์ปดํฌ๋ํธ์์ ๊ธฐ๋ณธ fetch๋ฅผ ์ฌ์ฉํ๋ ๋์ ๋ค์์ฒ๋ผ ๋ช ์ํ๋ ๊ฒ์ด๋ค.
await fetch(url, { cache: "no-store" })
๋๋
await fetch(url, { next: { revalidate: 0 } })
์ด ๋ ๋ฐฉ์์ ์์ ํ ์บ์ฑ์ ๋ฌดํจํํ๋ค.
๋ค๋ง, ๋ชจ๋ ์์ฒญ์์ no-store๋ฅผ ์ฐ๋ ๊ฒ์ ์ฑ๋ฅ ๋ฉด์์ ํฐ ์์ค์ด ์๋ค.
2) Server Actions์์ revalidatePath ๋๋ revalidateTag ์ฌ์ฉ
Server Actions๋ฅผ ํตํด ๋ฐ์ดํฐ๊ฐ ๋ณ๊ฒฝ๋๋ฉด UI๊ฐ ์ต์ ์ํ๋ฅผ ๊ฐ์ ธ์ฌ ์ ์๋๋ก ๊ฐ์ ๋ก revalidation์ ํธ๋ฆฌ๊ฑฐํด์ผ ํ๋ค.
๊ฐ์ฅ ๊ถ์ฅ๋๋ ๋ฐฉ๋ฒ์ ๋ค์ ๋ ๊ฐ์ง์ด๋ค.
revalidatePath
import { revalidatePath } from "next/cache";
export async function updateName(id: string, name: string) {
await prisma.user.update({ where: { id }, data: { name } });
revalidatePath("/users"); // ํน์ ๊ฒฝ๋ก ๋ค์ ๋ ๋๋ง
}
revalidateTag
fetch์ ํ๊ทธ๋ฅผ ๋ถ์ด๊ณ revalidateTag๋ฅผ ํธ์ถํ๋ ๋ฐฉ์์ด๋ค.
// ์๋ฒ ์ปดํฌ๋ํธ
await fetch(url, { next: { tags: ["users"] } });
// ์๋ฒ ์ก์
revalidateTag("users");
์ด ๋ฐฉ์์ ๊ฐ์ฅ ์์ ์ ์ธ UI ๊ฐฑ์ ๋ฐฉ์์ ์ ๊ณตํ๋ค.
3) Route Handler(API)์ Server Actions์ ์ญํ ์ ๋ถ๋ฆฌ
๋ง์ ๊ฐ๋ฐ์๋ค์ด ํํ ๊ฒช๋ ๋ฌธ์ ๋ ๋ค์ ๊ตฌ์กฐ์์ ๋ฐ์ํ๋ค.
- ๋ฐ์ดํฐ ์กฐํ: ์๋ฒ ์ปดํฌ๋ํธ fetch
- ๋ฐ์ดํฐ ์์ : ์๋ฒ ์ก์
- ์ฌ์กฐํ: ํด๋ผ์ด์ธํธ fetch
์ด ๊ตฌ์กฐ๋ ์บ์ ์ผ๊ด์ฑ์ ๋ณด์ฅํ์ง ๋ชปํ๋ค.
์ด๋ ํด๊ฒฐ ์ ๋ต์ ๋งค์ฐ ๊ฐ๋จํ๋ค.
๋ฐ์ดํฐ ์ฝ๊ธฐ์ ์ฐ๊ธฐ๋ฅผ โRoute Handler(API)โ ์ค์ฌ์ผ๋ก ํตํฉํ๋ ๊ฒ์ด๋ค.
์:
// GET /api/users
export async function GET() {
const users = await prisma.user.findMany();
return Response.json(users);
}
๊ทธ๋ฆฌ๊ณ fetch ํ ๋:
fetch("/api/users", { cache: "no-store" });
Server Actions๋ ์ค์ง mutate ์ญํ ๋ง ํ๋๋ก ๊ตฌ์ฑํ๋ค.
์ด๋ ๊ฒ ๋ถ๋ฆฌํ๋ฉด ์บ์ฑ ์ถฉ๋์ด ํฌ๊ฒ ์ค์ด๋ ๋ค.
4) ์๋ฒ ์ก์ ๋ด๋ถ์์ ๊ฐ๋ฅํ fetch ํธ์ถ์ ํผํ๋ค
๋ง์ ๊ฐ๋ฐ์๊ฐ ์๋ฒ ์ก์ ๋ด๋ถ์์ fetch๋ฅผ ์ฌํ์ฉํ๋ ค๊ณ ํ๋ค.
await fetch("/api/user/1", { cache: "force-cache" });
๊ทธ๋ฌ๋ Server Actions๋ ๊ธฐ๋ณธ์ ์ผ๋ก Next.js์ fetch ์บ์์ ์ถฉ๋ํ ์ ์์ผ๋ฉฐ, ๋ถํ์ํ ์ค๋ณต ์์ ์ ๋ง๋ ๋ค.
์๋ฒ ์ก์ ์์๋ ๋ฐ์ดํฐ ์์ค(DB, Prisma ๋ฑ)์ ์ง์ ์ ๊ทผํ๋ ํธ์ด ์์ ํ๋ค.
4. ์ค์ ์ ์ฉ ์์
์๋๋ Next.js 14 ๊ธฐ์ค์ผ๋ก ๊ฐ์ฅ ์์ ์ ์ด๊ณ ๊ถ์ฅ๋๋ ๊ตฌ์กฐ๋ค.
1) ์๋ฒ ์ปดํฌ๋ํธ ๋ฐ์ดํฐ ํจ์นญ
// app/users/page.tsx
export default async function UsersPage() {
const users = await fetch("https://example.com/api/users", {next: { tags: ["users"] },
}).then((res) => res.json());
return <UsersList users={users} />;
}
2) ์๋ฒ ์ก์ ์์ ๋ฐ์ดํฐ ์์ + revalidateTag ํธ์ถ
"use server";
import { prisma } from "@/lib/prisma";
import { revalidateTag } from "next/cache";
export async function updateUserName(id: string, name: string) {
await prisma.user.update({ where: { id }, data: { name } });
revalidateTag("users");
}
3) ํด๋ผ์ด์ธํธ์์ Server Actions ํธ์ถ
"use client";
import { updateUserName } from "./actions";
export default function EditForm() {
async function onSubmit() {await updateUserName("1", "New Name");
}
return <button onClick={onSubmit}>Update</button>;
}
์ด ๊ตฌ์กฐ๋ ์บ์ฑ ์ถฉ๋ ์์ด ํญ์ ์ต์ UI๋ฅผ ์ ์งํ ์ ์๋ค.
5. ๊ฒฐ๋ก
Next.js์ Server Actions์ fetch๋ ๋งค์ฐ ๊ฐ๋ ฅํ ๊ธฐ๋ฅ์ด์ง๋ง, ์๋ก ๋ค๋ฅธ ์บ์ฑ ์ ๋ต์ ๊ฐ๋ณ์ ์ผ๋ก ๊ฐ์ง๊ณ ์๊ธฐ ๋๋ฌธ์ ํผ์ฉ๋ ๋ ๋ฌธ์ ๊ฐ ๋ฐ์ํ๊ธฐ ์ฝ๋ค.
ํต์ฌ ์์ฝ์ ๋ค์๊ณผ ๊ฐ๋ค.
- Next.js ์๋ฒ fetch๋ ๊ธฐ๋ณธ์ ์ผ๋ก ์บ์ฑ๋๋ค.
- Server Actions๋ ์๋์ผ๋ก revalidate๋ฅผ ํ์ง ์๋๋ค.
- Server Action ์คํ ํ UI๋ฅผ ๊ฐฑ์ ํ๋ ค๋ฉด revalidatePath ๋๋ revalidateTag๋ฅผ ์ฌ์ฉํด์ผ ํ๋ค.
- fetch ์์ฒญ์ no-store ๋๋ revalidate ์ต์ ์ ๋ช ์์ ์ผ๋ก ์ค์ ํด์ผ ํ๋ค.
- Route Handler์ Server Actions์ ์ฑ ์์ ๋ถ๋ฆฌํ๋ฉด ์บ์ฑ ์ถฉ๋์ ์ค์ผ ์ ์๋ค.
์ด ์๋ฆฌ๋ฅผ ์ดํดํ๋ฉด App Router ๊ธฐ๋ฐ์ Next.js ์ ํ๋ฆฌ์ผ์ด์ ์์ ์์ ์ ์ด๊ณ ์์ธก ๊ฐ๋ฅํ ๋ฐ์ดํฐ ํ๋ฆ์ ์ค๊ณํ ์ ์๋ค.