06 · Developer Guide · 레시피
게시판 동적 라우트Dynamic Board Route
새소식·주보·함즐함울·큐티 등 모든 게시판형 페이지의 골격. 데이터 모듈 + generateStaticParams + 이전/다음 네비 세 부분으로 구성합니다.
언제 쓰는가
- 외부 게시판(예: pcltv.org)에서 가져온 게시물 N건을 사이트 안에서 정적 페이지로 펼칠 때
- 이미지·본문이 있는 게시물 상세 페이지 (갤러리·라이트박스 포함)
- 관리자가 직접 입력하기 전, 시안 단계의 가짜 게시판이 필요한 경우
① 데이터 모듈
lib/preview/*.ts에 게시물 배열과 헬퍼 함수(가져오기·인접 게시물)를 한 파일로 둡니다. 라우트와 인덱스 페이지가 같은 모듈을 참조합니다.
lib/preview/news.tsts
// lib/preview/news.ts
export type NewsPost = {
id: string;
date: string;
title: string;
paragraphs?: string[];
images?: string[];
thumb?: string;
archiveHref?: string;
};
export const NEWS_POSTS: NewsPost[] = [
{
id: "news-182961",
date: "2026.04.28",
title: "2026년 4월 새가족 환영회",
paragraphs: ["...", "..."],
images: ["/news/182961/01.jpg", "/news/182961/02.jpg" /* ... */],
thumb: "/news/182961/01.jpg",
archiveHref: "https://www.pcltv.org/Board/Detail/1090/182961",
},
// ...
];
export function getNewsPost(id: string) {
return NEWS_POSTS.find((p) => p.id === id);
}
export function getAdjacentPosts(id: string) {
const idx = NEWS_POSTS.findIndex((p) => p.id === id);
return {
prev: idx > 0 ? NEWS_POSTS[idx - 1] : null,
next: idx >= 0 && idx < NEWS_POSTS.length - 1 ? NEWS_POSTS[idx + 1] : null,
};
}② 동적 라우트 + generateStaticParams
[id] 폴더 안에 page.tsx를 두고 generateStaticParams로 모든 id를 미리 빌드합니다. 클라이언트 요청마다 정적 HTML이 반환되어 빠릅니다.
app/(site)/site/yard/news/[id]/page.tsxtsx
// app/(site)/site/yard/news/[id]/page.tsx
import { notFound } from "next/navigation";
import { NEWS_POSTS, getAdjacentPosts, getNewsPost } from "@/lib/preview/news";
export function generateStaticParams() {
return NEWS_POSTS.map((p) => ({ id: p.id }));
}
export async function generateMetadata({
params,
}: {
params: Promise<{ id: string }>;
}) {
const { id } = await params;
const post = getNewsPost(id);
return { title: post?.title ?? "게시물" };
}
export default async function NewsPostPage({
params,
}: {
params: Promise<{ id: string }>;
}) {
const { id } = await params;
const post = getNewsPost(id);
if (!post) notFound();
const { prev, next } = getAdjacentPosts(id);
return (
<article>
<h1>{post.title}</h1>
<p>{post.date}</p>
{post.paragraphs?.map((p, i) => <p key={i}>{p}</p>)}
{/* 갤러리 · 이전/다음 네비 */}
</article>
);
}③ 인덱스 카드 → 상세 링크
news/page.tsxtsx
// app/(site)/site/yard/news/page.tsx — 인덱스
import Link from "next/link";
import { NEWS_POSTS } from "@/lib/preview/news";
export default function NewsListPage() {
return (
<ul>
{NEWS_POSTS.map((p) => (
<li key={p.id}>
<Link href={`/site/yard/news/${p.id}`}>
{p.title}
</Link>
</li>
))}
</ul>
);
}실 사이트에서 적용한 변형 4개
네 가지 게시판이 모두 같은 패턴으로 동작합니다. 변형 포인트는 데이터 구조뿐.
/site/yard/news/[id]— 새소식 + 갤러리 (이미지 N장)/site/yard/bulletin/[slug]— 주보 4면 (slug = 발행일)/site/yard/happy-board/[no]— 함즐함울 호별 (no = 호수)/site/nurture/qt/[slug]— 오늘의 큐티 (slug = 날짜)