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 = 날짜)
Need Help

도움이 필요하신가요?

주님의교회 PCL 디자인 시스템을 적용하시다가 막히는 부분이 있다면, 다음 경로로 직접 문의하실 수 있습니다.