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 에서
...
블록만 추출해 렌더링한다. // 이렇게 하면 Export index.html 과 동일한 main/app-root DOM 구조를 재사용할 수 있다. const mainMatch = fullHtml.match(//); const mainHtml = mainMatch ? mainMatch[0] : ""; return ; }