← Назад к вопросам

Как работал с CMS?

2.2 Middle🔥 161 комментариев
#Soft Skills и рабочие процессы

Комментарии (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, не через традиционные шаблоны.