Інтеграція 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 днів