1765038880
2025-12-03 10:59:00
最新の Web 開発では、サイトのパフォーマンスとコンテンツ エディターに必要な柔軟性の間で難しいバランスをとることが必要になることがよくあります。これに対処するために、私と仲間の Optimizely OMVP は、 グラハム・カー Optimizely SaaS CMS の強力な機能を最大限に活用しながら、スピードとシンプルさを優先するソリューションを構築するための概念実証 (POC) に着手しました。
課題: パフォーマンスとプレビュー
当社のフロントエンドアーキテクト/リーダー、 アリン・トーマスでは、静的に生成されながらも CMS のライブ プレビュー機能と完全な互換性があり、膨張を最小限に抑えたソリューションを見つけるという明確な課題を設定します。 Next.js のようなフレームワークは人気がありますが、多くの場合、コンテンツの多いサイトでは必ずしも必要ではない大量のクライアント側 JavaScript (ハイドレーション) が付属しています。もっと無駄のないものが欲しかったのです。
目標は、次のようなサイトを構築することでした。
- 静的に生成: 超高速のロード時間とセキュリティを実現します。
- 軽量: より重いフレームワークによる「水和税」を回避します。
- 編集者に優しい: 編集者が公開前に Optimizely Visual Builder でコンテンツのライブ プレビューを確認できるようにします。
ソリューション: 11ty と Optimizely SaaS CMS の出会い
私たちが選んだのは 11ty(イレブンティ) 静的サイト ジェネレーター (SSG) として。 11ty はそのシンプルさと柔軟性で有名です。特定のクライアント側アーキテクチャを強制する他のフレームワークとは異なり、11ty では出力を完全に制御できます。純粋な HTML が生成されます。これはまさに肥大化を最小限に抑えるために必要なものです。
11ty を Optimizely に接続するために、 SaaS CMSを最適化する そして コンテンツJS SDK。
SaaS CMS とコンテンツ グラフを最適化する
当社のソリューションは、ヘッドレス コンテンツ リポジトリとして Optimizely SaaS CMS を中心としています。私たちが使用するのは コンテンツグラフを最適化する、高性能 GraphQL API を使用してコンテンツを取得します。これにより、必要なときに必要なものを正確にクエリできるようになり、ビルド プロセスの効率を維持できます。
コンテンツ JS SDK
の コンテンツJS SDK 私たちのアーキテクチャにとって不可欠なものでした。これにより、タイプセーフなビルダー パターンを使用して、コード内でコンテンツ モデルを直接定義できるようになりました。
たとえば、「ボタン ブロック」の定義は、次のような単純な TypeScript 定義になります。
import { contentType } from '@optimizely/cms-sdk';
export const ButtonBlock = contentType({
key: 'ButtonBlock',
baseType: '_block',
displayName: 'Button Block',
properties: {
Text: { type: 'string', displayName: 'Text' },
Url: { type: 'url', displayName: 'Url' },
},
});
このアプローチにより、コードベースがコンテンツ モデルと確実に同期され、優れた開発者の人間工学が提供され、実行時エラーが軽減されます。
スケーラビリティを考慮した構造
プロジェクトの成長に合わせて健全性を維持するために、コンテンツ戦略と最新のコンポーネント設計原則の両方を反映する厳密なディレクトリ構造を採用しました。
モデルフォルダー
src/models ディレクトリは、すべてのコンテンツ定義の信頼できる情報源です。 CMS のさまざまなタイプのコンテンツを反映するように構成しました。
- src/モデル/ページ/: ページ全体の定義 (ArticlePage、LandingPage など)。
- ソース/モデル/エクスペリエンス/: Visual Builder で使用されるコンポジションベースのコンテンツ タイプ。
- src/モデル/コンポーネント/: 再利用可能なコンテンツ ブロック (ButtonBlock、TextBlock など)。
アトミックコンポーネントライブラリ
フロントエンドの実装には、次のものを採用しました。 アトミックデザイン。この方法論では、UI を最小の基本単位に分割し、サイト全体での一貫性と再利用性を確保します。
src/components ディレクトリは次のような構造になっています。
- 原子/: ボタン、入力、テキスト ブロックなどの基本的な構成要素。
- 分子/: 連携する原子のグループ (検索フォームなど)。
- 生物/: ヘッダー、フッター、ヒーロー バナーなどの複雑な UI セクション。
- テンプレート/: すべてをつなぎ合わせたページレベルのレイアウト。
この懸念事項の明確な分離により、開発者は個々のコンポーネントを独立して作業できると同時に、コンポーネントがより大規模なシステムに完全に適合することを保証できます。また、自然に遵守を促進します。 単一責任の原則それぞれの原子、分子、生物には明確で焦点を当てた目的があるからです。
110 ページネーションによる動的ルーティング
静的サイト ジェネレーターの課題の 1 つは、CMS からの動的コンテンツを静的ルートにマッピングすることです。 11ty はこれをエレガントに処理します。 ページネーション 特徴。私たちはコンテンツ リポジトリ全体を単一の「ページ分割された」データ セットとして扱い、各アイテムがページになります。
まず、グローバル データ ファイル src/_data/routes.js 内のルーティング可能なコンテンツをすべてフェッチします。
// src/_data/routes.js
module.exports = async function () {
// ... client setup ...
// Fetch all content items that have a URL
const query = getRoutePagesQuery(blockFragments);
const data = await client.request(query);
// Filter out items without a default URL
const pages = data._Content.items.filter(item =>
item._metadata &&
item._metadata.url &&
item._metadata.url.default
);
return pages;
};
次に、このデータを反復処理する単一のテンプレート src/pages.11ty.ts を作成します。サイズを 1 に設定すると、11ty は Routes 配列内の項目ごとに個別の HTML ファイルを生成します。パーマリンク機能により、ファイルは CMS で定義されている正しいパスに保存されます。
// src/pages.11ty.ts
export const data = {
pagination: {
data: 'routes',
size: 1,
alias: 'contentItem',
addAllPagesToCollections: true,
},
layout: 'base.11ty.ts',
permalink: (data: any) => {
const item = data.pagination.items[0];
return item._metadata.url.default;
},
// ...
};
export function render(data: any): string {
const item = data.pagination.items[0];
return ComponentFactory(item);
}
このパターンは信じられないほど強力です。つまり、ルートを手動で構成したり、ページ タイプごとに個別のテンプレートを作成したりする必要がありません。編集者が Optimizely に新しいページを追加すると、11ty はそれらを自動的に検出し、対応する静的ページを構築します。
コンポーネントファクトリー
CMS から返されるさまざまなコンテンツ タイプを処理するために、 コンポーネントファクトリー。この関数はディスパッチャーとして機能し、各項目の __typename またはコンテンツ タイプを検査し、適切なコンポーネントをレンダリングします。
// src/components/ComponentFactory.ts
export function ComponentFactory(content: ContentItem): string {
// Handle CompositionComponentNode wrapper
if (content.component) {
return ComponentFactory(content.component);
}
const types = content._metadata?.types || [];
const typename = content.__typename || '';
// Dynamic component lookup from registry
const ComponentView = getView(typename);
if (ComponentView) {
return ComponentView(content);
}
// Fallback for unknown types
return `
${content.Title || content._metadata?.displayName || 'Unknown'}
Unknown component type: ${typename}
`;
}
このアーキテクチャは、 オープン/クローズの原則。コアのルーティングやレンダリング ロジックを変更せずに、新しいコンポーネント タイプをレジストリに追加できます。
ライブプレビューを有効にする
この POC で最も重要な部分は、11ty の静的な性質が編集エクスペリエンスを妨げないようにすることでした。専用の機能を実装しました エクスプレス プレビュー サーバー これは 11ty ビルドと並行して実行されます。
このサーバーは、Optimizely Visual Builder と静的テンプレートの間の動的なブリッジとして機能します。
Express サーバーの実装
私たちが選んだのは 急行 その堅牢性と使いやすさのために。サーバーは、いくつかの重要な役割を処理します。
- HMAC認証: 安全に取得します 下書き プレビュー トークンを使用したコンテンツ。
- コンテキスト認識: 「編集モード」(data-epi-* 属性の挿入) と「プレビュー モード」を切り替えます。
- リアルタイム更新: コンテンツの公開時に Webhook をリッスンして 110 回の再構築をトリガーします。
src/preview/server.ts でプレビュー リクエストを処理する方法を簡単に示します。
// src/preview/server.ts
app.get('/preview/:contentKey', async (req: Request, res: Response) => {
const { contentKey } = req.params;
const { preview_token, ctx } = req.query;
try {
// 1. Set context mode for Visual Builder (edit vs preview)
const contextMode =
ctx === 'edit' ? 'edit' :
ctx === 'preview' ? 'preview' :
null;
setContextMode(contextMode);
// 2. Set preview token to enable access to draft content
if (preview_token && typeof preview_token === 'string') {
previewClient.setPreviewToken(preview_token);
}
// 3. Fetch content dynamically
const content = await previewClient.getContentByKey(contentKey);
if (!content) {
return res.status(404).send(renderNotFound(contentKey));
}
// 4. Render using the same shared templates as 11ty
const html = renderContent(content);
res.send(html);
} catch (error) {
res.status(500).send(renderError('Server Error', String(error)));
}
});
Webhook の処理
静的サイトと公開されたコンテンツの同期を保つために、Webhook エンドポイントも公開しました。編集者がページを公開すると、Optimizely がサーバーに通知し、11ty サイトのデバウンス再構築がトリガーされます。
// src/preview/server.ts
app.post('/webhook/content-published', (req: Request, res: Response) => {
// ... validation ...
// Debounce rebuilds to handle rapid updates efficiently
if (rebuildTimeout) {
clearTimeout(rebuildTimeout);
}
rebuildTimeout = setTimeout(() => {
triggerRebuild();
}, REBUILD_DEBOUNCE_MS);
res.json({ status: 'ok', message: 'Rebuild scheduled' });
});
この 2 つのアプローチにより、本番環境の訪問者用の静的サイトと編集者用の動的でインタラクティブなプレビューという両方の長所が得られます。
Next.js よりも 11ty を使用する理由
Next.js は素晴らしいフレームワークですが、この特定のユースケースでは、11ty が明確な利点を提供しました。
- デフォルトでクライアント側 JS はゼロ: 11ty は、シングル ページ アプリケーション (SPA) が必要であることを前提としていません。 HTML を提供します。 JavaScript が必要な場合は追加します。これにより、バンドル サイズが大幅に小さくなり、インタラクティブ化までの時間 (TTI) が短縮されます。
- ビルド速度: 11ty は静的ページの構築が信じられないほど高速で、これは数千ページに拡張する場合に重要です。
- シンプルさ: 学習曲線がより緩やかになり、アーキテクチャを推論するのが容易になります。デバッグする必要のある複雑な水分補給ロジックはありません。
結論
この POC では、Optimizely SaaS CMS を使用して、動的で編集しやすい最新の Web サイトを構築するために重い JavaScript フレームワークが必要ないことを実証しました。 11ty のパワーとシンプルさを Optimizely の堅牢な API と組み合わせることで、開発者とコンテンツ編集者の両方に満足していただけるソリューションを提供できました。
私たちは、静的サイトのパフォーマンスと CMS 駆動アプリケーションの動的編集機能の両方の長所を実現しました。
最終結果はここで確認できます。 11ty-netcel-saas.netlify.app
完全なソース コードは GitHub で入手できます。 github.com/MineshS/11ty (現在はプライベート リポジトリですが、追加できるように Git ユーザー名をお知らせください)
2025 年 12 月 3 日
#11ty #を使用した軽量の #Optimizely #SaaS #CMS #ソリューションの構築