要約: Genspark等のAIツールが生成した完成形HTMLとCSSを、microCMSのテキストエリアにそのまま格納し、Next.js App Routerで表示する実装パターンを紹介します。CSSスコーピングでスタイル衝突を防ぎつつ、記事ごとに異なるデザインを実現できます。
この記事でわかること
- microCMSにAI生成HTML(Genspark等)を「そのまま格納」するアプローチ
- Next.jsでCSSスコーピングを使ってスタイル衝突を防ぐ実装
- このアプローチのメリット・デメリットと適用判断
はじめに
microCMSをはじめとするヘッドレスCMSの一般的な使い方といえば、構造化されたデータをAPIで取得し、フロントエンドで整形して表示するというパターンです。
たとえばブログ記事なら、タイトル・本文・サムネイル画像・カテゴリなどを別々のフィールドに保存し、Next.jsなどのフロントエンドで見た目を組み立てるかと思います。
【一般的なmicroCMSの使い方】
microCMS側:
- タイトル: テキストフィールド
- 本文: リッチエディタ
- サムネイル: 画像フィールド
- カテゴリ: コンテンツ参照
フロントエンド側:
- APIでデータ取得
- コンポーネントで整形・表示
しかし、あるメディアサイトの開発で、この「常識」とは真逆のアプローチを取る機会がありました。
AIが生成した完成形のHTMLとCSSを、そのままmicroCMSに格納する
という方法です。
本記事では、このアプローチに至った背景、具体的な実装方法、そしてメリット・デメリットを紹介します。
背景: AIが生成したHTMLをそのままNext.jsのWebアプリに表示させたい
あるメディアサイトの開発で、以下のような要望が出てきました。
- Gensparkを使って図解入りの記事を自由に生成したい(サイトのトンマナは維持しつつ)
- エンジニアの手を借りずに記事を公開したい(デプロイ作業なしで)
- 既存のNext.jsアプリにスムーズに組み込みたい
これを実現する方法を検討した結果、以下の流れにたどり着きました。
Gensparkが生成したHTMLを、CMSからAPI経由で取得してそのまま表示できないか?
↓
社内で利用実績のあるmicroCMSなら、既存のNext.jsアプリに少ない工数で組み込めるかも!
Gensparkが生成する「完成形のHTML」
Gensparkは、プロンプトを入力するだけでデザイン込みの完成形HTMLを生成してくれます。
<!-- Gensparkが生成するHTMLの例 -->
<div class="article-container">
<span class="category-badge">カテゴリ名</span>
<h1>記事タイトル</h1>
<p class="lead-text">リード文がここに入ります...</p>
<!-- 以下、本文が続く -->
</div>
このHTMLには、対応するCSSも含まれています。
/* Gensparkが生成するCSSの例 */
:root { --primary-color: #2563eb; }
.article-container { max-width: 800px; margin: 0 auto; }
.category-badge { background: var(--primary-color); color: white; }
方針: HTMLとCSSをそのままmicroCMSに格納する
microCMSには「テキストエリア」というフィールドタイプがあり、複数行のプレーンテキストを格納できます。少し強引にも思えるかもしれませんが、これを使えばHTMLもCSSもそのまま保存できると考えました。
【実装方針】
Genspark生成HTML → microCMSのテキストエリア(そのまま格納)
Genspark生成CSS → microCMSのテキストエリア(そのまま格納)
↓
Next.jsで取得してそのまま表示
一般的なヘッドレスCMSの使い方(構造化データを管理してフロントで整形)とは異なりますが、「完成形をそのまま表示する」という目的には、このシンプルなアプローチが最適でした。
AI生成HTML埋め込み: 他のアプローチとの比較
AI生成HTMLを既存サイトに埋め込む方法はいくつかあります。今回のアプローチを選んだ理由を、他の選択肢との比較で説明します。
| アプローチ | メリット | デメリット | 採用判断 |
|---|---|---|---|
<style>タグ + スコーピング |
実装シンプル、SEO影響なし | @media内のセレクタは未対応 | 採用 |
| Shadow DOM | 完全なスタイル分離 | SEOに悪影響、実装が複雑 | 不採用 |
| iframe | 完全な分離 | 高さ調整が困難、SEOに悪影響 | 不採用 |
| CSS Modules | Next.jsとの親和性が高い | 動的CSSには不向き | 不採用 |
採用したアプローチの制限事項
今回採用した「<style>タグ + スコーピング」には以下の制限があります。
-
@mediaクエリ内のセレクタはスコープされない - 複雑なセレクタ(
:has(),:where()など)は未対応の場合がある - ネストされたat-rule(
@supports内の@mediaなど)は考慮していない
これらの制限は、AI生成CSSの傾向(シンプルなセレクタが多い)を踏まえ、実用上は問題ないと判断しました。より厳密なスコーピングが必要な場合は、PostCSSプラグインなどの利用を検討してください。
microCMSスキーマ設計: HTMLとCSSを別々のフィールドに格納
APIスキーマの構成
記事コンテンツのAPIスキーマを以下のように設計しました。
| フィールドID | 表示名 | 種類 | 用途 |
|---|---|---|---|
title |
記事タイトル | テキストフィールド | SEO・OGP用 |
category |
カテゴリー | コンテンツ参照 | フィルタリング用 |
author |
著者名 | テキストフィールド | メタ情報用 |
publishedAt |
公開日 | 日時 | メタ情報用 |
thumbnail |
サムネイル画像 | 画像 | 一覧表示・OGP用 |
aiGeneratedHtml |
AI生成HTML | テキストエリア | 記事本文HTML |
aiGeneratedStyle |
AI生成スタイル | テキストエリア | 記事専用CSS |
tableOfContents |
目次 | 繰り返し | SEO description用 |
ポイントは以下の2つです。
-
aiGeneratedHtml: AI生成HTMLをそのまま格納 -
aiGeneratedStyle: AI生成CSSをそのまま格納
なぜHTMLとCSSを分離したか
HTMLとCSSを別フィールドにした理由は、CSSのスコーピング(後述) を行うためです。
AI生成CSSには :root や body などグローバルに影響するセレクタが含まれており、そのまま適用するとサイト全体のスタイルが崩れてしまいます。
CSSを別フィールドに分離することで、Next.js側でスコーピング処理を挟んでから適用できるようになります。
Next.js App Routerでの表示実装
CSSスコーピングの必要性
AI生成CSSをそのまま <style> タグで挿入すると、大きな問題が発生します。
/* AI生成CSSの例 */
:root { --primary-color: #2563eb; }
body { font-family: "Inter", sans-serif; }
* { margin: 0; padding: 0; }
これらのセレクタはサイト全体に影響してしまいます。ヘッダーやフッター、他のページのスタイルまで書き換わってしまうのです。
CSSスコーピング関数の実装
解決策として、CSSセレクタを特定のクラス配下にスコープする関数を実装しました。
/**
* CSSをスコープ化する関数
* セレクタを指定されたクラス配下に限定する
*/
function scopeCSS(css: string, scopeClass: string): string {
return css
.split('\n')
.map((line) => {
const trimmed = line.trim();
// 空行・コメント行はそのまま
if (!trimmed || trimmed.startsWith('/*') || trimmed.startsWith('*')) {
return line;
}
// セレクタの行({ を含む)を処理
if (trimmed.includes('{')) {
let selector = trimmed.substring(0, trimmed.indexOf('{')).trim();
// :root と body はスコープクラスに置換
if (selector === ':root' || selector === 'body') {
selector = `.${scopeClass}`;
}
// @media などのat-ruleはそのまま
else if (selector.startsWith('@')) {
return line;
}
// 通常のセレクタにはプレフィックスを追加
else {
selector = `.${scopeClass} ${selector}`;
}
return line.substring(0, line.indexOf(trimmed)) + selector + ' {';
}
return line;
})
.join('\n');
}
この関数により、以下のように変換されます。
/* 変換前 */
:root { --primary-color: #2563eb; }
body { font-family: "Inter", sans-serif; }
.article-container { max-width: 800px; }
/* 変換後 */
.ai-content-wrapper { --primary-color: #2563eb; }
.ai-content-wrapper { font-family: "Inter", sans-serif; }
.ai-content-wrapper .article-container { max-width: 800px; }
ページコンポーネントの実装
Next.js App Routerでの実装例です。
// app/articles/[id]/page.tsx
export default async function ArticlePage({
params
}: {
params: Promise<{ id: string }>
}) {
const { id } = await params;
// microCMSから記事データを取得
const article = await getArticle(id);
// CSSをスコープ化
const scopedStyles = scopeCSS(article.aiGeneratedStyle, 'ai-content-wrapper');
return (
<div className="min-h-screen bg-white">
{/* スコープ化したCSSを動的に挿入 */}
<style>
{scopedStyles}
</style>
{/* AI生成HTMLをレンダリング */}
<div
className="ai-content-wrapper"
dangerouslySetInnerHTML={{ __html: article.aiGeneratedHtml }}
/>
</div>
);
}
この実装により、記事ごとに完全に異なるデザインを持つことが可能になります。通常のCMSでは「サイト全体で統一されたデザイン」になりますが、このアプローチでは各記事が独自のデザインを持てるという特徴があります。
メリット・デメリット
メリット
- 運用効率の向上: AI生成 → HTMLとCSSをコピペ → 完了。分解・再構築の手間がない
- デザインの自由度: 記事ごとに異なるデザインを適用可能。特集ページやキャンペーンページに有効
- AIの進化に追従しやすい: 「HTMLとCSSを格納する」構造は変わらないため、新しいAIツールへの移行が容易
デメリット
-
セキュリティへの配慮:
dangerouslySetInnerHTMLを使用するため、信頼できるソースからのみHTMLを受け入れる運用が必要 -
SEO対応の工夫: メタデータ用フィールド(タイトル、目次など)を別途用意し、
generateMetadataで動的に設定する必要がある - 検索・フィルタリングの制限: 構造化された検索(「この見出しを含む記事」など)は難しくなる
まとめ
本記事では、microCMSを「HTML置き場」として使い、AI生成記事をNext.jsで表示する実装パターンを紹介しました。
ポイント:
- AI生成の完成形HTMLとCSSを、それぞれテキストエリアに格納
- CSSはスコーピング関数で処理してから適用
- 記事ごとに異なるデザインを実現可能
このアプローチが適しているケース:
- AIでコンテンツを量産したい
- 記事ごとに異なるデザインが必要
- 特集ページ・キャンペーンページを効率的に作りたい
適していないケース:
- コンテンツの構造化検索が必要
- 統一されたデザインシステムを維持したい
ヘッドレスCMSの「構造化データを管理する」という一般的な使い方とは異なりますが、AI生成コンテンツを扱う場面では有効なアプローチです。
「microCMSは何でも格納できる」 という柔軟性を活かした、新しいCMS活用パターンとして参考になれば幸いです。