Эх сурвалжийг харах

sitemap 에 개별 게시글 추가 및 lastmod 보강

목록 페이지가 클라이언트 렌더링이라 초기 HTML 에 글 링크가 없어,
sitemap 에도 개별 게시글이 없으면 크롤러가 글을 발견할 경로가 아예 없었다.
(운영 sitemap 11 건 = 고정 7 + 게시판 4, 게시글 0 건)

- fetchAllPosts 로 게시글 전체 수집 (perPage 100, 최대 50 페이지, id 중복 제거)
- 비밀글(isSecret) 제외, 공지/전체공지는 포함
- lastModified 를 게시글 createdAt 으로 지정. 홈/게시판 목록은 갱신 시각
- /login, /register 제거 (색인 가치 없음)
- 빈 catch 를 console.error 로 교체 — 게시판이 통째로 빠져도 알 수 없었다

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
KIM-JINO5 11 цаг өмнө
parent
commit
72b0fe323f
1 өөрчлөгдсөн 76 нэмэгдсэн , 17 устгасан
  1. 76 17
      app/sitemap.ts

+ 76 - 17
app/sitemap.ts

@@ -1,36 +1,95 @@
 import { MetadataRoute } from 'next';
 import { MetadataRoute } from 'next';
-import { fetchBoardList } from '@/lib/api/forum/board';
+import { fetchBoardList, fetchAllPosts } from '@/lib/api/forum/board';
 
 
 const BASE_URL = 'https://antooza.com';
 const BASE_URL = 'https://antooza.com';
 
 
+// 게시글 수집 설정. sitemap 규격 상한은 50,000 건이라 여유가 있지만 무한 루프는 막는다.
+const POSTS_PER_PAGE = 100;
+const MAX_POST_PAGES = 50;
+
 export default async function sitemap(): Promise<MetadataRoute.Sitemap> {
 export default async function sitemap(): Promise<MetadataRoute.Sitemap> {
+	const now = new Date();
+
 	// 정적 페이지
 	// 정적 페이지
+	// /login, /register 는 색인 가치가 없어 제외한다.
 	const staticPages: MetadataRoute.Sitemap = [
 	const staticPages: MetadataRoute.Sitemap = [
-		{ url: BASE_URL, changeFrequency: 'daily', priority: 1.0 },
-		{ url: `${BASE_URL}/login`, changeFrequency: 'monthly', priority: 0.3 },
-		{ url: `${BASE_URL}/register`, changeFrequency: 'monthly', priority: 0.3 },
+		{ url: BASE_URL, lastModified: now, changeFrequency: 'daily', priority: 1.0 },
 		{ url: `${BASE_URL}/docs`, changeFrequency: 'weekly', priority: 0.5 },
 		{ url: `${BASE_URL}/docs`, changeFrequency: 'weekly', priority: 0.5 },
 		{ url: `${BASE_URL}/support/faq`, changeFrequency: 'monthly', priority: 0.4 },
 		{ url: `${BASE_URL}/support/faq`, changeFrequency: 'monthly', priority: 0.4 },
 		{ url: `${BASE_URL}/support/guide`, changeFrequency: 'monthly', priority: 0.4 },
 		{ url: `${BASE_URL}/support/guide`, changeFrequency: 'monthly', priority: 0.4 },
 		{ url: `${BASE_URL}/support/contact`, changeFrequency: 'monthly', priority: 0.3 },
 		{ url: `${BASE_URL}/support/contact`, changeFrequency: 'monthly', priority: 0.3 },
 	];
 	];
 
 
-	// 동적: 게시판 목록
-	let boardPages: MetadataRoute.Sitemap = [];
+	const [boardPages, postPages] = await Promise.all([fetchBoardPages(now), fetchPostPages()]);
+
+	return [...staticPages, ...boardPages, ...postPages];
+}
+
+// 게시판 목록
+async function fetchBoardPages(now: Date): Promise<MetadataRoute.Sitemap> {
 	try {
 	try {
 		const res = await fetchBoardList();
 		const res = await fetchBoardList();
-		if (res.success && res.data?.list) {
-			boardPages = res.data.list
-				.filter(b => b.isActive)
-				.map(b => ({
-					url: `${BASE_URL}/board/${b.code}`,
-					changeFrequency: 'daily' as const,
-					priority: 0.8,
-				}));
+
+		if (!res.success || !res.data?.list) {
+			return [];
 		}
 		}
-	} catch {
 
 
+		return res.data.list
+			.filter(board => board.isActive)
+			.map(board => ({
+				url: `${BASE_URL}/board/${board.code}`,
+				lastModified: now,
+				changeFrequency: 'daily' as const,
+				priority: 0.8,
+			}));
+	} catch (error) {
+		// 조용히 삼키면 sitemap 에서 게시판이 통째로 빠져도 알 수 없다.
+		console.error('[sitemap] 게시판 목록 조회 실패', error);
+		return [];
+	}
+}
+
+// 게시글
+// 목록 페이지가 클라이언트 렌더링이라 초기 HTML 에 글 링크가 없다.
+// sitemap 에 넣지 않으면 크롤러가 개별 게시글을 발견할 경로가 없다.
+async function fetchPostPages(): Promise<MetadataRoute.Sitemap> {
+	const entries: MetadataRoute.Sitemap = [];
+	const seen = new Set<number>();
+
+	try {
+		for (let page = 1; page <= MAX_POST_PAGES; page++) {
+			const res = await fetchAllPosts({ page, perPage: POSTS_PER_PAGE });
+
+			if (!res.success || !res.data) {
+				break;
+			}
+
+			// 공지/전체공지도 실제로 열리는 페이지이므로 함께 담는다.
+			const posts = [...(res.data.speaker ?? []), ...(res.data.notice ?? []), ...(res.data.list ?? [])];
+
+			for (const post of posts) {
+				// 비밀글은 비로그인 사용자가 열 수 없어 제외한다.
+				if (post.isSecret || seen.has(post.id)) {
+					continue;
+				}
+
+				seen.add(post.id);
+				entries.push({
+					url: `${BASE_URL}/post/${post.id}`,
+					lastModified: post.createdAt ? new Date(post.createdAt) : undefined,
+					changeFrequency: 'weekly',
+					priority: 0.6,
+				});
+			}
+
+			// 목록이 한 페이지 분량보다 적으면 마지막 페이지다.
+			if ((res.data.list?.length ?? 0) < POSTS_PER_PAGE) {
+				break;
+			}
+		}
+	} catch (error) {
+		console.error('[sitemap] 게시글 조회 실패', error);
 	}
 	}
 
 
-	return [...staticPages, ...boardPages];
-}
+	return entries;
+}