</>
  • Главная
  • Обо мне
  • Навыки
  • Опыт
  • Кейсы
  • Блог
  • Проекты
madrimov.uz

Keling, jiddiy
tizim quramiz

madrimov5014@gmail.com→
</>© 2026 Madrimov Xudoshukur
GitHubTelegramKanalEmail
← Блог
17-июля, 2026

Notion как headless CMS: без зависимостей и платных сервисов

BackendArxitekturaNext.js

Выбор CMS для блога — одно из самых времязатратных решений владельца небольшого сайта. Ставишь Strapi — нужно содержать сервер. Берёшь Contentful — ждёт платный тариф. Идёшь с markdown-файлами — каждый пост означает commit + deploy.

Я выбрал третий путь: Notion сам является CMS, а сайт читает его через API. Уже год madrimov.uz работает по этой схеме — ноль серверов, ноль дополнительных расходов, а публикация статьи сводится к отметке чекбокса "Published".

Почему именно Notion?

  • Опыт письма уже готов — редактор, мобильное приложение, история версий.
  • Колонки database идеальны для метаданных: slug, теги, язык, дата — всё это property.
  • API бесплатный и стабильный; на объёмах личного блога в лимиты не упрёшься.

Важно: для этого решения не нужен ни один npm-пакет. SDK @notionhq/client удобен, но обычный fetch делает ту же работу.

Ядро: клиент на 60 строк

Чтение database Notion — один POST-запрос. Главное — не забыть про пагинацию:

const NOTION_API = "https://api.notion.com/v1";

async function queryAll(dbId: string) {
	const pages = [];
	let cursor: string | undefined;
	do {
		const res = await fetch(`${NOTION_API}/databases/${dbId}/query`, {
			method: "POST",
			headers: {
				Authorization: `Bearer ${process.env.NOTION_TOKEN}`,
				"Notion-Version": "2022-06-28",
				"Content-Type": "application/json",
			},
			body: JSON.stringify({
				filter: { property: "Published", checkbox: { equals: true } },
				...(cursor ? { start_cursor: cursor } : {}),
			}),
			next: { revalidate: 300 },
		});
		if (!res.ok) break;
		const data = await res.json();
		pages.push(...data.results);
		cursor = data.has_more ? data.next_cursor : undefined;
	} while (cursor);
	return pages;
}

Обрати внимание: next: { revalidate: 300 } — это механизм ISR в Next.js. Сайт обращается к Notion не на каждый запрос, а раз в 5 минут. Даже если Notion упадёт, сайт продолжит отдавать старый кеш.

Рендеринг блоков

Notion возвращает содержимое страницы как дерево "блоков": paragraph, heading, код, списки. Превратить их в React-элементы — простой switch:

function renderBlock(block: any) {
	switch (block.type) {
		case "paragraph":
			return <p>{richText(block.paragraph.rich_text)}</p>;
		case "heading_2":
			return <h2>{richText(block.heading_2.rich_text)}</h2>;
		case "code":
			return (
				<pre>
					<code>{plain(block.code.rich_text)}</code>
				</pre>
			);
		default:
			return null;
	}
}

Поддерживать всё не обязательно — хватит 8-10 типов блоков, которые ты реально используешь. Если остальные вернут null, сайт не упадёт.

Три правила спустя год

  1. Строго синхронизируй имена property с кодом. Переименуешь колонку в Notion — сайт молча вернёт пустоту. Это самый частый "баг".
  2. Всегда `try/catch` + пустой массив как fallback. Если Notion API замедлился или отдал 500, страница блога с кодом 200 и пустым списком лучше, чем ошибка 500.
  3. Помни про срок signed URL для картинок. Файлы Notion приходят с подписанной ссылкой на ~1 час — если твой интервал ISR короче, проблема решается сама.

Итог

Notion + fetch + ISR — достаточная и не избыточная CMS для личного блога. Без сервера, без подписки, контент без деплоя. Следующим этапом поверх этой схемы строится автоматическая дистрибуция (Telegram, LinkedIn) — но это тема другой статьи.

Такие статьи — каждую неделю

Новые статьи, практический инженерный опыт и проекты — в Telegram-канале.

Подписаться на канал →

Похожие статьи

  • Почему в multi-core Node.js ломаются in-process события и cron — и как это правильно решить18-июня, 2026
  • От NestJS + Prisma к Bun + Hono + Drizzle: почему я перешёл16-июня, 2026
  • Bun + Hono: год в production14-июня, 2026