Интеграция Contentful с фронтендом (React/Next.js/Gatsby)

Наша компания занимается разработкой, поддержкой и обслуживанием сайтов любой сложности. От простых одностраничных сайтов до масштабных кластерных систем построенных на микро сервисах. Опыт разработчиков подтвержден сертификатами от вендоров.

Разработка и обслуживание любых видов сайтов:

Информационные сайты или веб-приложения
Сайты визитки, landing page, корпоративные сайты, онлайн каталоги, квиз, промо-сайты, блоги, новостные ресурсы, информационные порталы, форумы, агрегаторы
Сайты или веб-приложения электронной коммерции
Интернет-магазины, B2B-порталы, маркетплейсы, онлайн-обменники, кэшбэк-сайты, биржи, дропшиппинг-платформы, парсеры товаров
Веб-приложения для управления бизнес-процессами
CRM-системы, ERP-системы, корпоративные порталы, системы управления производством, парсеры информации
Сайты или веб-приложения электронных услуг
Доски объявлений, онлайн-школы, онлайн-кинотеатры, конструкторы сайтов, порталы предоставления электронных услуг, видеохостинги, тематические порталы

Это лишь некоторые из технических типов сайтов, с которыми мы работаем, и каждый из них может иметь свои специфические особенности и функциональность, а также быть адаптированным под конкретные потребности и цели клиента

Предлагаемые услуги
Показано 1 из 1 услугВсе 2065 услуг
Интеграция Contentful с фронтендом (React/Next.js/Gatsby)
Средняя
~3-5 рабочих дней
Часто задаваемые вопросы

Наши компетенции:

Этапы разработки

Последние работы

  • image_website-b2b-advance_0.png
    Разработка сайта компании B2B ADVANCE
    1262
  • image_web-applications_feedme_466_0.webp
    Разработка веб-приложения для компании FEEDME
    1171
  • image_websites_belfingroup_462_0.webp
    Разработка веб-сайта для компании БЕЛФИНГРУПП
    874
  • image_ecommerce_furnoro_435_0.webp
    Разработка интернет магазина для компании FURNORO
    1094
  • image_crm_enviok_479_0.webp
    Разработка веб-приложения для компании Enviok
    831
  • image_bitrix-bitrix-24-1c_fixper_448_0.png
    Разработка веб-сайта для компании ФИКСПЕР
    851

Интеграция Contentful с фронтендом (React/Next.js/Gatsby)

Contentful как headless CMS отдаёт контент через JSON API, и задача интеграции — не просто «забрать данные», а правильно организовать типизацию, кэширование, инкрементальную регенерацию и preview-режим. Подходы для Next.js, Gatsby и чистого React существенно различаются.

Типизация из Content Types

Ручное написание TypeScript-интерфейсов для Contentful-моделей — источник ошибок. Используем @contentful/rich-text-types и генерацию типов через cf-content-types-generator:

npx cf-content-types-generator \
  --spaceId $CONTENTFUL_SPACE_ID \
  --token $CONTENTFUL_MANAGEMENT_TOKEN \
  --out src/types/contentful.ts \
  --v10  # совместимость с contentful SDK v10+

Результат — строго типизированные интерфейсы для всех Content Types:

// Автогенерированный тип
export interface TypeBlogPostFields {
  title: EntryFieldTypes.Symbol;
  slug: EntryFieldTypes.Symbol;
  body: EntryFieldTypes.RichText;
  heroImage: EntryFieldTypes.AssetLink;
  author: EntryFieldTypes.EntryLink<TypeAuthorSkeleton>;
  publishedAt: EntryFieldTypes.Date;
  tags: EntryFieldTypes.Array<EntryFieldTypes.Symbol>;
}

export type TypeBlogPostSkeleton = EntrySkeletonType<TypeBlogPostFields, 'blogPost'>;

Next.js App Router: Server Components + ISR

// lib/contentful.ts
import { createClient, type Entry } from 'contentful';
import type { TypeBlogPostSkeleton } from '@/types/contentful';

const client = createClient({
  space: process.env.CONTENTFUL_SPACE_ID!,
  accessToken: process.env.CONTENTFUL_DELIVERY_TOKEN!,
});

export async function getBlogPosts() {
  const entries = await client.getEntries<TypeBlogPostSkeleton>({
    content_type: 'blogPost',
    order: ['-fields.publishedAt'],
    include: 2, // глубина связанных записей
  });
  return entries.items;
}

// app/blog/[slug]/page.tsx
export const revalidate = 3600; // ISR: регенерация каждый час

export async function generateStaticParams() {
  const posts = await getBlogPosts();
  return posts.map((post) => ({ slug: post.fields.slug }));
}

export default async function BlogPostPage({ params }) {
  const post = await getPostBySlug(params.slug);
  return <BlogPost post={post} />;
}

Rich Text рендеринг

Rich Text поле возвращает AST-структуру, не HTML. Для рендера используем @contentful/rich-text-react-renderer:

import { documentToReactComponents, Options } from '@contentful/rich-text-react-renderer';
import { BLOCKS, INLINES, MARKS } from '@contentful/rich-text-types';
import Image from 'next/image';

const renderOptions: Options = {
  renderNode: {
    [BLOCKS.EMBEDDED_ASSET]: (node) => {
      const asset = node.data.target;
      return (
        <Image
          src={`https:${asset.fields.file.url}`}
          width={asset.fields.file.details.image.width}
          height={asset.fields.file.details.image.height}
          alt={asset.fields.description || asset.fields.title}
          className="rounded-lg my-6"
        />
      );
    },
    [BLOCKS.EMBEDDED_ENTRY]: (node) => {
      const entry = node.data.target;
      if (entry.sys.contentType.sys.id === 'codeBlock') {
        return <CodeBlock code={entry.fields.code} lang={entry.fields.language} />;
      }
      return null;
    },
    [INLINES.HYPERLINK]: (node, children) => (
      <a href={node.data.uri} target="_blank" rel="noopener noreferrer">
        {children}
      </a>
    ),
  },
  renderMark: {
    [MARKS.CODE]: (text) => <code className="bg-muted px-1 rounded">{text}</code>,
  },
};

export const RichText = ({ document }) =>
  documentToReactComponents(document, renderOptions);

Gatsby: Source Plugin

// gatsby-config.ts
{
  resolve: 'gatsby-source-contentful',
  options: {
    spaceId: process.env.CONTENTFUL_SPACE_ID,
    accessToken: process.env.CONTENTFUL_DELIVERY_TOKEN,
    enableTags: true,
    // Отдельный токен для preview-среды в Gatsby Cloud
    host: process.env.GATSBY_CONTENTFUL_HOST || 'cdn.contentful.com',
  },
}

GraphQL-запрос в Gatsby Page:

query BlogPostQuery($slug: String!) {
  contentfulBlogPost(slug: { eq: $slug }) {
    title
    publishedAt
    body {
      raw
      references {
        ... on ContentfulAsset {
          contentful_id
          __typename
          url
          width
          height
          description
        }
      }
    }
    heroImage {
      gatsbyImageData(
        width: 1200
        placeholder: BLURRED
        formats: [AUTO, WEBP, AVIF]
      )
    }
  }
}

Webhook + On-demand ISR

Для обновления страниц без деплоя настраиваем Contentful Webhook на Next.js revalidation endpoint:

// app/api/revalidate/route.ts
import { revalidatePath, revalidateTag } from 'next/cache';

export async function POST(request: Request) {
  const secret = request.headers.get('x-contentful-webhook-secret');
  if (secret !== process.env.CONTENTFUL_WEBHOOK_SECRET) {
    return Response.json({ error: 'Unauthorized' }, { status: 401 });
  }

  const body = await request.json();
  const contentType = body.sys?.contentType?.sys?.id;
  const slug = body.fields?.slug?.['en-US'];

  if (contentType === 'blogPost' && slug) {
    revalidatePath(`/blog/${slug}`);
    revalidateTag('blog-posts');
  }

  return Response.json({ revalidated: true });
}

Оптимизация изображений

Contentful Images API поддерживает трансформации через URL-параметры. Интеграция с Next.js Image:

// next.config.ts
images: {
  remotePatterns: [
    { protocol: 'https', hostname: 'images.ctfassets.net' },
    { protocol: 'https', hostname: 'downloads.ctfassets.net' },
  ],
}

// Компонент
const contentfulLoader = ({ src, width, quality }) =>
  `${src}?w=${width}&q=${quality || 75}&fm=webp`;

<Image
  loader={contentfulLoader}
  src={`https:${asset.fields.file.url}`}
  alt={alt}
  fill
  sizes="(max-width: 768px) 100vw, 50vw"
/>

Сроки типовой интеграции

Задача Время
Базовая настройка клиента + типизация 0.5 дня
Вывод списка и страниц одного Content Type 1 день
Rich Text рендерер с кастомными узлами 0.5–1 день
ISR + Webhook revalidation 0.5 дня
Preview Mode (Draft Mode) 0.5 дня
Полная интеграция (5–10 Content Types) 3–5 дней