Современная веб-разработка часто требует соблюдения сложного баланса между производительностью сайта и гибкостью, необходимой редакторам контента. Чтобы решить эту проблему, я и мой коллега из Optimizely OMVP Грэм Карр приступила к проверке концепции (POC) для создания решения, в котором приоритет отдается скорости и простоте, при этом полностью используя мощные возможности Optimizely SaaS CMS.
Задача: производительность против предварительного просмотра
Наш фронтенд-архитектор/ведущий специалист, Аллин Томаспоставили четкую задачу: найти решение с минимальным раздуванием, которое генерируется статически, но при этом полностью совместимо с возможностями предварительного просмотра CMS. Хотя такие платформы, как Next.js, популярны, они часто содержат значительный объем клиентского JavaScript (гидратации), который не всегда необходим для сайтов с большим количеством контента. Нам хотелось чего-то более компактного.
Целью было создать сайт, который:
- Статически созданный: для невероятно быстрой загрузки и безопасности.
- Легкий: Избегание «налога на гидратацию» более тяжелых фреймворков.
- Удобный для редактора: разрешение редакторам просматривать предварительный просмотр своего контента в Optimizely Visual Builder перед публикацией.
Решение: 11ty соответствует Optimize SaaS CMS
Мы выбрали 11ти (Одиннадцать) в качестве нашего генератора статических сайтов (SSG). 11ty известен своей простотой и гибкостью. В отличие от других фреймворков, которые заставляют вас использовать конкретную клиентскую архитектуру, 11ty дает вам полный контроль над выходными данными. Он генерирует чистый HTML, а это именно то, что нам нужно, чтобы свести раздувание к минимуму.
Чтобы связать 11ty с Optimizely, мы использовали Оптимизировать SaaS CMS и JS SDK для контента.
Оптимизация SaaS CMS и графика контента
Наше решение основано на Optimizely SaaS CMS в качестве автономного хранилища контента. Мы используем Оптимизировать график контентавысокопроизводительный API GraphQL для получения контента. Это позволяет нам запрашивать именно то, что нам нужно, и тогда, когда нам это нужно, сохраняя эффективность процесса сборки.
Контент 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:
- источник/модели/страницы/: определения для полных страниц (например, ArticlePage, LandingPage).
- источник/модели/опыт/: типы контента на основе композиции, используемые в Visual Builder.
- источник/модели/компоненты/: повторно используемые блоки контента (например, ButtonBlock, TextBlock).
Библиотека атомарных компонентов
Для реализации интерфейса мы использовали Атомный дизайн. Эта методология разбивает пользовательский интерфейс на мельчайшие фундаментальные единицы, обеспечивая согласованность и возможность повторного использования на сайте.
Наш каталог src/comComponents структурирован следующим образом:
- атомы/: основные строительные блоки, такие как кнопки, поля ввода и текстовые блоки.
- молекулы/: группы атомов, работающих вместе (например, форма поиска).
- организмы/: сложные разделы пользовательского интерфейса, такие как верхние и нижние колонтитулы и главные баннеры.
- шаблоны/: макеты на уровне страницы, объединяющие все вместе.
Такое четкое разделение задач позволяет разработчикам работать над отдельными компонентами изолированно, гарантируя, что они идеально вписываются в более крупную систему. Это также естественным образом поощряет соблюдение Принцип единой ответственностипоскольку каждый атом, молекула или организм имеет определенную и целенаправленную цель.
Динамическая маршрутизация с 11-кратной нумерацией страниц
Одной из проблем генератора статических сайтов является сопоставление динамического контента из 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 генерирует отдельный 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 и нашими статическими шаблонами.
Реализация экспресс-сервера
Мы выбрали Выражать за его надежность и простоту использования. Сервер выполняет несколько ключевых функций:
- HMAC-аутентификация: безопасно извлекает черновик контент с использованием токенов предварительного просмотра.
- Осведомленность о контексте: переключение между «Режимом редактирования» (вставка атрибутов data-epi-*) и «Режимом предварительного просмотра».
- Обновления в реальном времени: прослушивает веб-перехватчики для запуска 11ty перестроений при публикации контента.
Вот краткий обзор того, как мы обрабатываем запросы предварительного просмотра в 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)));
}
});
Обработка вебхуков
Чтобы синхронизировать статический сайт с опубликованным контентом, мы также предоставили конечную точку веб-перехватчика. Когда редактор публикует страницу, 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' });
});
Этот двойной подход дает нам лучшее из обоих миров: статический сайт для посетителей производства и динамический интерактивный предварительный просмотр для редакторов.
Почему 11ty вместо Next.js?
Хотя Next.js — фантастический фреймворк, для этого конкретного случая использования 11ty предлагает явные преимущества:
- Нулевой клиентский JS по умолчанию: 11ty не предполагает, что вам нужно одностраничное приложение (SPA). Он обслуживает HTML. Если вам нужен JavaScript, вы добавляете его. Это приводит к значительному уменьшению размеров пакетов и сокращению времени взаимодействия (TTI).
- Скорость сборки: 11ty невероятно быстро создает статические страницы, что имеет решающее значение при масштабировании до тысяч страниц.
- Простота: Кривая обучения более плавная, и об архитектуре легче рассуждать. Нет сложной логики гидратации, которую нужно отлаживать.
Заключение
Этот POC продемонстрировал, что вам не нужна тяжелая среда JavaScript для создания современного, динамичного и удобного для редактирования веб-сайта с помощью Optimizely SaaS CMS. Объединив мощь и простоту 11ty с надежными API-интерфейсами Optimizely, мы создали решение, которое понравится как разработчикам, так и редакторам контента.
Мы достигли лучшего из обоих миров: производительность статического сайта с возможностями динамического редактирования приложения на базе CMS.
Посмотреть окончательный результат можно здесь: 11ty-netcel-saas.netlify.app
Полный исходный код доступен на GitHub: github.com/MineshS/11ty (В настоящее время это частный репозиторий, но, пожалуйста, свяжитесь с именем пользователя Git, чтобы можно было добавить)
3 декабря 2025 г.
Читайте также
