feat: FormBlock 컨트롤러 정리 및 15.1 SEO/head 메타 관리 추가
CI / test (push) Failing after 7m19s
CI / pr_and_merge (push) Has been skipped

This commit is contained in:
2025-11-27 11:20:38 +09:00
parent 48e13d2a75
commit e16f8298ab
9 changed files with 341 additions and 147 deletions
+75 -2
View File
@@ -52,7 +52,9 @@ const escapeHtml = (value: string): string =>
const escapeAttr = (value: string): string => escapeHtml(value);
export const buildStaticHtml = (blocks: Block[], projectConfig?: ProjectConfig): string => {
const pageTitleRaw = (projectConfig?.title ?? "").trim() || "Page Builder Export";
const baseTitleRaw = (projectConfig?.title ?? "").trim() || "Page Builder Export";
const seoTitleRaw = (projectConfig?.seoTitle ?? "").trim();
const pageTitleRaw = seoTitleRaw || baseTitleRaw;
const pageTitle = escapeHtml(pageTitleRaw);
const headExtraRaw = (projectConfig?.headHtml ?? "").trim();
@@ -84,6 +86,77 @@ export const buildStaticHtml = (blocks: Block[], projectConfig?: ProjectConfig):
const trackingRaw = (projectConfig?.trackingScript ?? "").trim();
const trackingHtml = trackingRaw ? `\n${trackingRaw}\n` : "";
const seoDescriptionRaw = (projectConfig?.seoDescription ?? "").trim();
const seoOgImageRaw = (projectConfig?.seoOgImageUrl ?? "").trim();
const seoCanonicalRaw = (projectConfig?.seoCanonicalUrl ?? "").trim();
const seoNoIndex = projectConfig?.seoNoIndex === true;
const seoHeadParts: string[] = [];
if (seoDescriptionRaw) {
seoHeadParts.push(
` <meta name="description" content="${escapeAttr(seoDescriptionRaw)}" />`,
);
}
if (pageTitleRaw) {
seoHeadParts.push(
` <meta property="og:title" content="${escapeAttr(pageTitleRaw)}" />`,
);
}
if (seoDescriptionRaw) {
seoHeadParts.push(
` <meta property="og:description" content="${escapeAttr(seoDescriptionRaw)}" />`,
);
}
seoHeadParts.push(` <meta property="og:type" content="website" />`);
if (seoOgImageRaw) {
seoHeadParts.push(
` <meta property="og:image" content="${escapeAttr(seoOgImageRaw)}" />`,
);
}
if (seoCanonicalRaw) {
seoHeadParts.push(
` <meta property="og:url" content="${escapeAttr(seoCanonicalRaw)}" />`,
);
}
// Twitter 카드 메타 태그
seoHeadParts.push(
` <meta name="twitter:card" content="summary_large_image" />`,
);
if (pageTitleRaw) {
seoHeadParts.push(
` <meta name="twitter:title" content="${escapeAttr(pageTitleRaw)}" />`,
);
}
if (seoDescriptionRaw) {
seoHeadParts.push(
` <meta name="twitter:description" content="${escapeAttr(seoDescriptionRaw)}" />`,
);
}
if (seoOgImageRaw) {
seoHeadParts.push(
` <meta name="twitter:image" content="${escapeAttr(seoOgImageRaw)}" />`,
);
}
if (seoCanonicalRaw) {
seoHeadParts.push(
` <link rel="canonical" href="${escapeAttr(seoCanonicalRaw)}" />`,
);
}
if (seoNoIndex) {
seoHeadParts.push(
` <meta name="robots" content="noindex, nofollow" />`,
);
}
const seoHeadHtml = seoHeadParts.length > 0 ? `\n${seoHeadParts.join("\n")}` : "";
const sectionBlocks = blocks.filter((b) => b.type === "section");
const rootBlocks = blocks.filter((b) => !b.sectionId && b.type !== "section");
@@ -515,7 +588,7 @@ export const buildStaticHtml = (blocks: Block[], projectConfig?: ProjectConfig):
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>${pageTitle}</title>
<link rel="stylesheet" href="./builder.css" />${headExtra}
<link rel="stylesheet" href="./builder.css" />${seoHeadHtml}${headExtra}
</head>
<body style="${bodyStyle}">
<main>