Комментарии (1)
🐱
claude-haiku-4.5PrepBro AI3 апр. 2026 г.(ред.)
Ответ сгенерирован нейросетью и может содержать ошибки
Мой опыт работы с CMS
Часто фронтенд разработчики сталкиваются с Content Management Systems. Расскажу о своём опыте и подходе.
1. Типы CMS которые я использовал
const cmsExperience = {
// Headless CMS
strapi: {
description: "Самоуправляемый Node.js CMS",
used_for: "Контент для Next.js приложения",
api_type: "REST/GraphQL",
experience: "2+ года"
},
contentful: {
description: "Облачный Headless CMS",
used_for: "Маркетинг сайты с динамическим контентом",
api_type: "GraphQL",
experience: "1+ год"
},
// Traditional CMS
wordpress: {
description: "WordPress с REST API",
used_for: "Блоги и новостные сайты",
api_type: "REST API",
experience: "Меньше всего, если честно"
},
// Database approach
supabase: {
description: "Firebase альтернатива + PostrgeSQL"
used_for: "Простые приложения где не нужна CMS",
api_type: "REST/Realtime",
experience: "текущий стек"
}
};
2. Почему я не люблю традиционные CMS
const headlessCmsAdvantages = {
// Проблемы с tradicional CMS
"Tied to backend template engine": {
problem: "Не могу использовать React, Vue и т.д.",
impact: "Очень ограничивающе для современного фронта"
},
"Monolithic architecture": {
problem: "CMS и фронт - одно целое",
impact: "Сложно масштабировать, обновлять"
},
"Performance issues": {
problem: "Часто медленнее, чем JAMstack",
impact: "Плохие Web Vitals"
},
// Headless CMS решает это
"Separation of concerns": {
benefit: "CMS только управляет контентом",
benefit2: "Фронт - отдельное приложение (React)"
},
"API-first approach": {
benefit: "Контент доступен через API",
benefit2: "Использую где угодно - веб, мобильное, VR"
}
};
3. Мой подход: интеграция Strapi с Next.js
// Пример: получение постов из Strapi
import { fetch } from "node-fetch";
const STRAPI_URL = "http://localhost:1337";
export async function getPosts() {
const response = await fetch(
`${STRAPI_URL}/api/posts?populate=*`,
{
headers: {
Authorization: `Bearer ${process.env.STRAPI_API_KEY}`
}
}
);
const data = await response.json();
return data.data; // Strapi возвращает data обёрнутые
}
// Использование в Next.js
export default async function BlogPage() {
const posts = await getPosts();
return (
<div>
{posts.map(post => (
<article key={post.id}>
<h2>{post.attributes.title}</h2>
<p>{post.attributes.description}</p>
<img src={`${STRAPI_URL}${post.attributes.image.data.attributes.url}`} />
</article>
))}
</div>
);
}
4. GraphQL запрос к Contentful
// Contentful использует GraphQL
import { graphql } from "graphql-request";
const query = graphql`
query GetBlogPosts {
blogPostCollection {
items {
id
title
slug
description
publishedDate
author {
name
email
}
image {
url
title
width
height
}
}
}
}
`;
// Использование
export async function getBlogPosts() {
const response = await request(
`https://graphql.contentful.com/content/v1/spaces/${SPACE_ID}`,
query,
{},
{ Authorization: `Bearer ${ACCESS_TOKEN}` }
);
return response.blogPostCollection.items;
}
5. Обработка мультимедиа
// CMS часто хранит картинки и файлы
// Нужно уметь обрабатывать правильно
export function CmsImage({ asset, alt }) {
const { url, title, width, height } = asset;
return (
<Image
src={url}
alt={alt || title}
width={width}
height={height}
// Оптимизация через Next.js Image
// (автоматический resize, lazy loading, WebP)
loading="lazy"
priority={false}
/>
);
}
// Для видео
export function CmsVideo({ url }) {
return (
<video
src={url}
controls
width="100%"
loading="lazy"
>
Your browser doesn't support HTML5 video
</video>
);
}
6. Кэширование контента
// CMS контент часто статичен - нужно кэшировать
import { unstable_cache } from "next/cache";
// Кэшируем результаты на 1 час
const getCachedPosts = unstable_cache(
async () => {
return await fetch(
`${STRAPI_URL}/api/posts?populate=*`,
{
headers: {
Authorization: `Bearer ${process.env.STRAPI_API_KEY}`
}
}
).then(r => r.json());
},
["blog-posts"],
{ revalidate: 3600 } // Перегенерируем каждый час
);
export default async function BlogPage() {
const { data } = await getCachedPosts();
return (
// Render posts
);
}
7. Preview/Draft режим
// CMS позволяет предпросмотреть контент перед публикацией
// У Strapi есть Draft and Publish feature
const getPosts = async (isDraft = false) => {
const query = new URLSearchParams();
if (isDraft) {
// Получаем включая черновики
query.append("status", "draft");
// Нужна авторизация
} else {
// Только опубликованные
query.append("status", "published");
}
return fetch(
`${STRAPI_URL}/api/posts?${query}`,
{
headers: isDraft ? {
Authorization: `Bearer ${process.env.STRAPI_API_KEY}`
} : {}
}
).then(r => r.json());
};
// Использование в Next.js
export const generateStaticParams = async () => {
const posts = await getPosts(false); // Только published
return posts.data.map(post => ({
slug: post.attributes.slug
}));
};
8. Типизация CMS данных
// TypeScript для безопасности
interface CmsPost {
id: string;
title: string;
slug: string;
content: string;
excerpt: string;
publishedAt: Date;
updatedAt: Date;
author: {
name: string;
email: string;
avatar: {
url: string;
width: number;
height: number;
};
};
categories: Array<{
id: string;
name: string;
slug: string;
}>;
featured: boolean;
image: {
url: string;
width: number;
height: number;
alt: string;
};
}
// Когда получаю данные из CMS
export async function getPosts(): Promise<CmsPost[]> {
const data = await fetch(`${STRAPI_URL}/api/posts`);
return data.json();
}
9. Обработка rich-text из CMS
// CMS часто хранит контент в JSON или Markdown
import { documentToReactComponents } from "@contentful/rich-text-react-renderer";
export function RichText({ content }) {
return documentToReactComponents(content);
}
// Или если это HTML
import DOMPurify from "isomorphic-dompurify";
export function HtmlContent({ html }) {
const cleanHtml = DOMPurify.sanitize(html);
return (
<div dangerouslySetInnerHTML={{ __html: cleanHtml }} />
);
}
// Или Markdown
import ReactMarkdown from "react-markdown";
export function MarkdownContent({ markdown }) {
return <ReactMarkdown>{markdown}</ReactMarkdown>;
}
10. Проблемы которые я решал
const problemsSolved = {
// Проблема: CMS API медленный
solution: "Использую caching, ISR (Incremental Static Regeneration)",
code: "revalidate: 3600 // Переген каждый час"
// Проблема: Контент неполный или невалидный
solution: "Валидирую данные перед использованием",
code: ""
// Проблема: Изображения огромные
solution: "Использую Next.js Image + CDN",
benefit: "Автоматическая оптимизация"
// Проблема: Очень много контента
solution: "Пагинация и infinite scroll",
code: "limit: 10, offset: page * 10"
};
Мой рекомендуемый стек
Для новых проектов я выбираю:
const recommendedStack = {
cms: "Strapi (если свой сервер) или Contentful (облако)",
frontend: "Next.js",
api: "REST или GraphQL",
caching: "Next.js ISR + browser cache",
database: "PostgreSQL (если Strapi)",
deployment: "Vercel (Next.js) + Heroku/Railway (CMS)"
};
Этот стек позволяет быстро разрабатывать, легко масштабировать и иметь отличную производительность. Главное - всегда использовать CMS через API, не через традиционные шаблоны.