Разработка сайта на CMS Contentful

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

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

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

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

Предлагаемые услуги
Показано 1 из 1 услугВсе 2065 услуг
Разработка сайта на CMS Contentful
Средняя
~1-2 недели
Часто задаваемые вопросы

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

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

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

  • 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

Разработка сайта на CMS Contentful

Contentful — одна из первых и наиболее зрелых headless CMS на рынке. Полностью SaaS: хранилище контента, CDN, APIs — в облаке Contentful. Контентная модель создаётся через GUI (Content Model) или через API/CLI. REST API (Content Delivery API, Content Management API) и GraphQL из коробки.

Архитектура

Contentful Studio (Web)
         ↕ CMA (Content Management API)
  Content Lake (Contentful)
         ↕ CDA (Content Delivery API) / GraphQL
Next.js / Nuxt / Remix / Mobile App
         ↕ Contentful Image API (CDN)

CDA — публичный API с CDN-кэшем. CMA — для создания/редактирования записей (серверный токен, не для фронтенда). Preview API — для черновиков.

Content Types

Content Types создаются через Contentful GUI: Content Model → Add Content Type. Поля:

  • Text, Rich Text, Number, Date, Media, Boolean
  • Reference (связь с другим content type)
  • Array (список reference или media)

Можно создать через CLI:

npm install -g contentful-cli
contentful login
contentful space export --space-id YOUR_SPACE_ID
# Экспортирует схему и контент в JSON

REST API (Content Delivery API)

# Список записей
GET https://cdn.contentful.com/spaces/{SPACE_ID}/environments/master/entries
    ?content_type=blogPost
    &fields.slug=my-post
    &include=3
Authorization: Bearer {CDA_TOKEN}

# Одна запись
GET .../entries/{ENTRY_ID}?include=2

# Ресурсы (медиа)
GET .../assets?order=sys.createdAt&limit=20

SDK для JavaScript

npm install contentful
// lib/contentful.ts
import { createClient, type EntryFieldTypes } from 'contentful'

export const contentfulClient = createClient({
  space: process.env.CONTENTFUL_SPACE_ID!,
  accessToken: process.env.CONTENTFUL_ACCESS_TOKEN!,
  environment: 'master',
})

// Preview client для черновиков
export const previewClient = createClient({
  space: process.env.CONTENTFUL_SPACE_ID!,
  accessToken: process.env.CONTENTFUL_PREVIEW_TOKEN!,
  host: 'preview.contentful.com',
})

// TypeScript интерфейсы (можно генерировать через cf-content-types-generator)
export interface BlogPostSkeleton {
  contentTypeId: 'blogPost'
  fields: {
    title: EntryFieldTypes.Text
    slug: EntryFieldTypes.Text
    content: EntryFieldTypes.RichText
    excerpt: EntryFieldTypes.Text
    publishedAt: EntryFieldTypes.Date
    heroImage: EntryFieldTypes.AssetLink
    author: EntryFieldTypes.EntryLink<AuthorSkeleton>
    tags: EntryFieldTypes.Array<EntryFieldTypes.Symbol>
  }
}

Next.js App Router интеграция

// app/blog/[slug]/page.tsx
import { contentfulClient } from '@/lib/contentful'
import type { BlogPostSkeleton } from '@/lib/contentful'
import { documentToReactComponents } from '@contentful/rich-text-react-renderer'
import { BLOCKS, INLINES } from '@contentful/rich-text-types'
import { notFound } from 'next/navigation'

const richTextOptions = {
  renderNode: {
    [BLOCKS.EMBEDDED_ASSET]: (node: any) => (
      <img
        src={`https:${node.data.target.fields.file.url}`}
        alt={node.data.target.fields.title}
        className="rounded-lg my-6"
      />
    ),
    [INLINES.HYPERLINK]: (node: any, children: React.ReactNode) => (
      <a href={node.data.uri} className="text-blue-600 hover:underline">{children}</a>
    ),
  },
}

export default async function BlogPostPage({ params }: { params: { slug: string } }) {
  const entries = await contentfulClient.getEntries<BlogPostSkeleton>({
    content_type: 'blogPost',
    'fields.slug': params.slug,
    include: 2,
    limit: 1,
  })

  const post = entries.items[0]
  if (!post) notFound()

  const { title, content, heroImage, author } = post.fields

  return (
    <article>
      <h1>{title}</h1>
      {heroImage && typeof heroImage !== 'string' && (
        <img
          src={`https:${heroImage.fields.file?.url}`}
          alt={typeof heroImage.fields.title === 'string' ? heroImage.fields.title : ''}
        />
      )}
      <div>{documentToReactComponents(content, richTextOptions)}</div>
    </article>
  )
}

export async function generateStaticParams() {
  const entries = await contentfulClient.getEntries<BlogPostSkeleton>({
    content_type: 'blogPost',
    select: ['fields.slug'],
    limit: 1000,
  })
  return entries.items.map(entry => ({ slug: entry.fields.slug }))
}

export const revalidate = 3600

GraphQL API

const POSTS_QUERY = `
  query GetPosts($limit: Int, $locale: String) {
    blogPostCollection(limit: $limit, locale: $locale, order: publishedAt_DESC) {
      items {
        title
        slug
        excerpt
        publishedAt
        heroImage { url title }
        author { name }
      }
    }
  }
`

async function fetchGraphQL(query: string, variables = {}) {
  const response = await fetch(
    `https://graphql.contentful.com/content/v1/spaces/${process.env.CONTENTFUL_SPACE_ID}/`,
    {
      method: 'POST',
      headers: {
        'Content-Type': 'application/json',
        Authorization: `Bearer ${process.env.CONTENTFUL_ACCESS_TOKEN}`,
      },
      body: JSON.stringify({ query, variables }),
      next: { tags: ['contentful'], revalidate: 3600 },
    }
  )
  return response.json()
}

const { data } = await fetchGraphQL(POSTS_QUERY, { limit: 10, locale: 'ru' })
const posts = data.blogPostCollection.items

Вебхук для ISR

В Contentful: Settings → Webhooks → Add Webhook:

  • URL: https://yoursite.com/api/revalidate/contentful
  • Trigger: Entry Publish, Entry Unpublish, Asset Publish
// app/api/revalidate/contentful/route.ts
import { revalidateTag } from 'next/cache'

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

  const body = await req.json()
  const contentType = body.sys?.contentType?.sys?.id

  revalidateTag('contentful')
  if (contentType) revalidateTag(contentType)

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

Сравнение с Sanity/Directus/Strapi

Критерий Contentful Sanity Strapi Directus
Hosting SaaS only SaaS only Self-hosted / Cloud Self-hosted / Cloud
Бесплатный план 25k records, 2 locale 10k documents, 3 users ≤ open source ≤ open source
Rich text Rich Text (Contentful) Portable Text (кастом) Markdown/Draft.js JSON
Real-time Нет Да Нет Да (WebSocket)
Типичная цена От $300/мес Team От $15/мес Бесплатно (self-hosted) Бесплатно (self-hosted)

Сроки

Базовый сайт на Contentful с 3–5 content types и Next.js — 1,5–2 недели. Сложный мультиязычный проект с Preview Mode и GraphQL — 3–4 недели.