๐ Nextjs15 App Router ์์ํ๊ธฐ 3
โข2025๋ 9์ 30์ผ ํ์์ผ
โก ๋ผ์ฐํธ ์ธ๊ทธ๋จผํธ ์ต์
Next.js์ ํ์ด์ง์ ๋ ์ด์์์์ ๋์์ ์ ์ดํ๋ ์ธ๊ทธ๋จผํธ ์ต์ ๋ค์ด์์. dynamic์ ํ์ด์ง์ ๋ ๋๋ง ๋ฐฉ์์, revalidate๋ ์บ์ ๊ฐฑ์ ์ฃผ๊ธฐ๋ฅผ, fetchCache๋ ๋ฐ์ดํฐ ๊ฐ์ ธ์ค๊ธฐ ์ ๋ต์ ์ค์ ํด์. runtime์ ์คํ ํ๊ฒฝ์ ์ง์ ํ ์ ์์ด์.
// ํ์ด์ง ์๋จ์ ์์น
export const dynamic = "";
export const revalidate = 60; // ์ด ๋จ์๋ก ์ค์
export const fetchCache = "force-cache";
export const runtime = "nodejs"; // or "edge"์ฃผ์ ์ฌํญ
force-static ์ค์ ์ ๋์ ๋ฐ์ดํฐ๊ฐ ํ์ํ ํ์ด์ง์์ ๋ฌธ์ ๋ฅผ ์ผ์ผํฌ ์ ์์ด์. Edge runtime์ ์ ํ๋ Node.js API๋ง ์ฌ์ฉํ ์ ์์ผ๋ ์ฃผ์ํ์ธ์!
๐ฆ ํด๋ผ์ด์ธํธ ๋ผ์ฐํฐ ์บ์
๋ธ๋ผ์ฐ์ ๋ฉ๋ชจ๋ฆฌ์ ์ ์ฅ๋๋ ์์ ์ ์ฅ์์์. Router Cache๋ prefetch๋ ํ์ด์ง ์ธ๊ทธ๋จผํธ๋ฅผ ์ ์ฅํ๊ณ , Server Cache๋ React Server Component ํ์ด๋ก๋๋ฅผ ์ ์ฅํด์. useSelectedLayoutSegment()๋ usePathname() ๊ฐ์ ํ
์ ์ฌ์ฉํ ๋๋ ์ด ์บ์๋ฅผ ํ์ฉํด์.
router.refresh(); // ์ ์ฒด ๋ผ์ฐํธ ์๋ก๊ณ ์นจ
router.push("/path", { forceOptimisticNavigation: false }); // ์ต์ ํ ๋ค๋น๊ฒ์ด์
๋นํ์ฑ๐ ์คํธ๋ฆฌ๋ฐ๊ณผ ์์คํ์ค
React 18์ Streaming SSR์ Next.js์์ ๊ตฌํํ ๊ธฐ๋ฅ์ด์์. ์ด๊ธฐ HTML์ ๋น ๋ฅด๊ฒ ์ ์กํ๊ณ , React Server Components ํ์ด๋ก๋๋ ์ ์ง์ ์ผ๋ก ์คํธ๋ฆฌ๋ฐํด์.
// app/posts/loading.tsx
export default function Loading() {
return <LoadingSkeleton />;
}
// ์ปดํฌ๋ํธ ๋ ๋ฒจ ์คํธ๋ฆฌ๋ฐ
import { Suspense } from "react";
export default function Posts() {
return (
<Suspense fallback={<LoadingSkeleton />}>
<SlowPostList /> {/* ๋น๋๊ธฐ ์ปดํฌ๋ํธ */}
</Suspense>
);
}โ ๏ธ ๋ณ๋ ฌ ๋ผ์ฐํธ์ ์ธํฐ์ ํ
๋ณ๋ ฌ ๋ผ์ฐํธ(@modal)๋ฅผ ์ฌ์ฉํ๋ฉด ๋
๋ฆฝ์ ์ธ ๋ผ์ฐํธ๋ฅผ ๋์์ ๋ ๋๋งํ ์ ์์ด์. ์ธํฐ์
ํ
๋ผ์ฐํธ๋ ํ์ฌ ๋ ์ด์์ ๋ด์์ ๋ค๋ฅธ ๋ผ์ฐํธ์ ์ฝํ
์ธ ๋ฅผ ํ์ํ ๋ ์ ์ฉํด์.
// app/@modal/(..)posts/[id]/page.tsx
// (..) ํ๊ธฐ๋ก ์์ ๊ฒฝ๋ก ์ธํฐ์
ํธ
export default function PostModal({ params }) {
return <PostDetail id={params.id} />;
}โ ๏ธ ์๋ฌ ํธ๋ค๋ง๊ณผ ๋ฆฌ์ปค๋ฒ๋ฆฌ
error.tsx๋ ํน์ ์ธ๊ทธ๋จผํธ์ ์๋ฌ๋ฅผ, global-error.tsx๋ ์ ์ฒด ์ ํ๋ฆฌ์ผ์ด์
์ ์น๋ช
์ ์ธ ์๋ฌ๋ฅผ ์ฒ๋ฆฌํด์. not-found.tsx๋ฅผ ์ฌ์ฉํ๋ฉด 404 ์๋ฌ ํ์ด์ง๋ฅผ ์ปค์คํฐ๋ง์ด์ฆํ ์ ์์ด์.
// app/posts/error.tsx
"use client";
export default function Error({
error,
reset,
}: {
error: Error & { digest?: string };
reset: () => void;
}) {
return (
<div>
<h2>๋ฌธ์ ๊ฐ ๋ฐ์ํ์ต๋๋ค!</h2>
<button onClick={() => reset()}>๋ค์ ์๋</button>
</div>
);
}// app/global-error.tsx
export default function GlobalError({
error,
reset,
}: {
error: Error & { digest?: string };
reset: () => void;
}) {
return (
<html>
<body>
<h2>์์์น ๋ชปํ ์ค๋ฅ๊ฐ ๋ฐ์ํ์ต๋๋ค</h2>
<button onClick={() => reset()}>๋ค์ ์๋</button>
</body>
</html>
);
}๐ ์ ๋ฆฌ
1๏ธโฃ dynamic, revalidate, fetchCache, runtime ๋ฑ์ ์ธ๊ทธ๋จผํธ ์ต์ ์ผ๋ก ํ์ด์ง์ ๋์์ ์ ์ดํ ์ ์์ด์!
2๏ธโฃ Router Cache์ Server Cache๋ฅผ ํตํด ํ์ด์ง ์ธ๊ทธ๋จผํธ์ ์๋ฒ ์ปดํฌ๋ํธ ๋ฐ์ดํฐ๋ฅผ ํจ์จ์ ์ผ๋ก ์ ์ฅํ๊ณ ๊ด๋ฆฌํด์.
3๏ธโฃ React 18์ Streaming SSR์ ํ์ฉํด ์ด๊ธฐ HTML์ ๋น ๋ฅด๊ฒ ์ ์กํ๊ณ React Server Components ํ์ด๋ก๋๋ ์ ์ง์ ์ผ๋ก ์ ๋ฌํด์.
4๏ธโฃ @modal๊ณผ ๊ฐ์ ๋ณ๋ ฌ ๋ผ์ฐํธ๋ก ๋ ๋ฆฝ์ ์ธ ๋ ๋๋ง์, (..) ํ๊ธฐ๋ก ๋ค๋ฅธ ๋ผ์ฐํธ์ ์ฝํ ์ธ ๋ฅผ ๊ฐ๋ก์ฑ์ ๋ณด์ฌ์ค ์ ์์ด์.
5๏ธโฃ error.tsx์ global-error.tsx๋ก ์ธ๊ทธ๋จผํธ๋ณ ๋๋ ์ ์ญ ์๋ฌ๋ฅผ ํจ๊ณผ์ ์ผ๋ก ๊ด๋ฆฌํ๊ณ ๋ณต๊ตฌํ ์ ์์ด์.
Afaik ยฉ 2025
์ธ๋ถ ๋งํฌ