81 lines
2.7 KiB
TypeScript
81 lines
2.7 KiB
TypeScript
import { notFound } from "next/navigation";
|
|
import { PrismaClient } from "@prisma/client";
|
|
|
|
import type { Block, ProjectConfig } from "@/features/editor/state/editorStore";
|
|
import { buildStaticHtml } from "@/features/export/buildStaticHtml";
|
|
import { getCachedPublicProject } from "@/features/projects/publicCache";
|
|
import PublicProjectPageClient from "./PublicProjectPageClient";
|
|
|
|
const prisma = new PrismaClient();
|
|
|
|
interface PageParams {
|
|
// Next.js App Router 에서는 params 가 Promise 로 전달되므로
|
|
// 서버 컴포넌트에서 사용하기 전에 await 로 한 번 풀어줘야 한다.
|
|
params: Promise<{ slug: string }>;
|
|
}
|
|
|
|
// /p/[slug] 공개 페이지
|
|
// - slug 에 해당하는 Project 의 contentJson 을 불러와 buildStaticHtml 로 정적 HTML 을 생성한다.
|
|
// - Export ZIP 에 포함되는 index.html 과 동일한 app-root DOM 구조를 사용해 렌더링한다.
|
|
// - 인증 없이 접근 가능하며, 폼 제출은 /api/forms/submit 을 통해 정적 Export 와 동일하게 동작한다.
|
|
export default async function PublicProjectPage({ params }: PageParams) {
|
|
// Next 가 넘겨주는 params 는 Promise 이므로, 먼저 await 해서 slug 를 꺼낸다.
|
|
const resolvedParams = await params;
|
|
const slugRaw = resolvedParams.slug ?? "";
|
|
const slug = slugRaw.toString().trim();
|
|
|
|
if (!slug) {
|
|
notFound();
|
|
}
|
|
|
|
// 1차로 메모리 기반 퍼블릭 캐시에서 스냅샷을 조회한다.
|
|
const cached = getCachedPublicProject(slug);
|
|
|
|
let blocks: Block[];
|
|
let title: string;
|
|
|
|
if (cached) {
|
|
blocks = cached.contentJson ?? [];
|
|
title = cached.title ?? "";
|
|
} else {
|
|
const project = await prisma.project.findUnique({
|
|
where: { slug },
|
|
select: {
|
|
title: true,
|
|
slug: true,
|
|
contentJson: true,
|
|
},
|
|
});
|
|
|
|
if (!project) {
|
|
notFound();
|
|
}
|
|
|
|
const rawContent = project.contentJson;
|
|
let contentBlocks: Block[] = [];
|
|
if (Array.isArray(rawContent)) {
|
|
contentBlocks = rawContent as unknown as Block[];
|
|
}
|
|
blocks = contentBlocks;
|
|
title = project.title;
|
|
}
|
|
|
|
// Export index.html 과 동일한 규칙으로 ProjectConfig 를 구성한다.
|
|
const projectConfig: ProjectConfig = {
|
|
title,
|
|
slug,
|
|
canvasPreset: "full",
|
|
canvasBgColorHex: "#020617",
|
|
bodyBgColorHex: "#020617",
|
|
} as ProjectConfig;
|
|
|
|
const fullHtml = buildStaticHtml(blocks, projectConfig);
|
|
|
|
// buildStaticHtml 이 생성한 index.html 에서 <main> ... </main> 블록만 추출해 렌더링한다.
|
|
// 이렇게 하면 Export index.html 과 동일한 main/app-root DOM 구조를 재사용할 수 있다.
|
|
const mainMatch = fullHtml.match(/<main[\s\S]*?<\/main>/);
|
|
const mainHtml = mainMatch ? mainMatch[0] : "";
|
|
|
|
return <PublicProjectPageClient html={mainHtml} />;
|
|
}
|