Notion можно использовать как базу данных для сайта — это не хак и не обходной путь, это официальный Notion API с полноценным CRUD. Правильный сценарий: контент-менеджер редактирует данные в удобном Notion-интерфейсе, сайт получает их через API. Это называется Headless CMS паттерн. Разберём, как это реализовать, и где граница разумного.
Что такое Notion Database
В Notion «база данных» (Database) — это таблица со страницами в качестве строк. Каждая строка — это одновременно запись в таблице и полноценная страница с контентом. У каждой строки есть свойства (Properties) — аналог колонок в SQL.
Типы свойств в Notion:
- Title — обязательное текстовое поле-заголовок
- Text, Number, Date, Checkbox — базовые типы
- Select, Multi-select — выпадающие списки
- Relation — связь с другой базой данных (аналог Foreign Key)
- Formula — вычисляемые поля
- Files & media — прикреплённые файлы
- Person — участники воркспейса
- URL, Email, Phone — специализированные текстовые поля
Пример структуры блога в Notion:
| Название статьи | Slug | Статус | Теги | Дата публикации | Автор | |---|---|---|---|---|---| | MongoDB: полный гайд | mongodb-guide | Published | MongoDB, NoSQL | 2026-03-01 | Иван | | Redis уязвимости | redis-security | Draft | Redis, Security | — | Мария |
Notion API: что умеет и как работает
Notion предоставляет REST API. Для работы с ним нужно:
- Создать интеграцию на notion.so/my-integrations
- Получить Internal Integration Token (Bearer token)
- Предоставить интеграции доступ к нужным страницам (Share → Add connections)
Базовые запросы через API
# Получить все записи из базы данных
curl -X POST https://api.notion.com/v1/databases/{database_id}/query \
-H "Authorization: Bearer secret_ваш_токен" \
-H "Notion-Version: 2022-06-28" \
-H "Content-Type: application/json" \
-d '{
"filter": {
"property": "Статус",
"select": { "equals": "Published" }
},
"sorts": [
{ "property": "Дата публикации", "direction": "descending" }
]
}'
# Получить содержимое страницы (блоки контента)
curl https://api.notion.com/v1/blocks/{page_id}/children \
-H "Authorization: Bearer secret_ваш_токен" \
-H "Notion-Version: 2022-06-28"
Реализация: Notion как CMS для Next.js блога
Установка
npm install @notionhq/client notion-to-md
Клиент и запросы к API
// lib/notion.ts
import { Client } from '@notionhq/client'
import { NotionToMarkdown } from 'notion-to-md'
const notion = new Client({
auth: process.env.NOTION_TOKEN
})
const n2m = new NotionToMarkdown({ notionClient: notion })
const DATABASE_ID = process.env.NOTION_DATABASE_ID!
// Получить все опубликованные статьи
export async function getAllPosts() {
const response = await notion.databases.query({
database_id: DATABASE_ID,
filter: {
property: 'Статус',
select: { equals: 'Published' }
},
sorts: [
{ property: 'Дата публикации', direction: 'descending' }
]
})
return response.results.map((page: any) => ({
id: page.id,
title: page.properties['Название статьи'].title[0]?.plain_text || '',
slug: page.properties['Slug'].rich_text[0]?.plain_text || '',
date: page.properties['Дата публикации'].date?.start || null,
tags: page.properties['Теги'].multi_select.map((t: any) => t.name),
author: page.properties['Автор'].people[0]?.name || ''
}))
}
// Получить статью по slug
export async function getPostBySlug(slug: string) {
const response = await notion.databases.query({
database_id: DATABASE_ID,
filter: {
property: 'Slug',
rich_text: { equals: slug }
}
})
if (!response.results.length) return null
const page = response.results[0] as any
const mdBlocks = await n2m.pageToMarkdown(page.id)
const markdown = n2m.toMarkdownString(mdBlocks)
return {
id: page.id,
title: page.properties['Название статьи'].title[0]?.plain_text || '',
content: markdown.parent,
date: page.properties['Дата публикации'].date?.start || null,
}
}
Страница списка статей
// app/blog/page.tsx
import { getAllPosts } from '@/lib/notion'
export default async function BlogPage() {
const posts = await getAllPosts()
return (
<div>
<h1>Блог</h1>
{posts.map(post => (
<article key={post.id}>
<h2><a href={`/blog/${post.slug}`}>{post.title}</a></h2>
<p>{post.date}</p>
<div>{post.tags.map(tag => <span key={tag}>{tag}</span>)}</div>
</article>
))}
</div>
)
}
Страница статьи с рендерингом Markdown
// app/blog/[slug]/page.tsx
import { getPostBySlug, getAllPosts } from '@/lib/notion'
import ReactMarkdown from 'react-markdown'
export async function generateStaticParams() {
const posts = await getAllPosts()
return posts.map(post => ({ slug: post.slug }))
}
export default async function PostPage({ params }: { params: { slug: string } }) {
const post = await getPostBySlug(params.slug)
if (!post) return <div>Статья не найдена</div>
return (
<article>
<h1>{post.title}</h1>
<time>{post.date}</time>
<ReactMarkdown>{post.content}</ReactMarkdown>
</article>
)
}
Реализация на Python (Flask/FastAPI)
pip install notion-client
# notion_client_example.py
from notion_client import Client
import os
notion = Client(auth=os.environ["NOTION_TOKEN"])
DATABASE_ID = os.environ["NOTION_DATABASE_ID"]
def get_published_posts():
response = notion.databases.query(
database_id=DATABASE_ID,
filter={
"property": "Статус",
"select": {"equals": "Published"}
},
sorts=[{
"property": "Дата публикации",
"direction": "descending"
}]
)
posts = []
for page in response["results"]:
props = page["properties"]
posts.append({
"id": page["id"],
"title": props["Название статьи"]["title"][0]["plain_text"]
if props["Название статьи"]["title"] else "",
"slug": props["Slug"]["rich_text"][0]["plain_text"]
if props["Slug"]["rich_text"] else "",
"date": props["Дата публикации"]["date"]["start"]
if props["Дата публикации"]["date"] else None,
})
return posts
def create_post(title: str, slug: str, content: str):
notion.pages.create(
parent={"database_id": DATABASE_ID},
properties={
"Название статьи": {"title": [{"text": {"content": title}}]},
"Slug": {"rich_text": [{"text": {"content": slug}}]},
"Статус": {"select": {"name": "Draft"}},
},
children=[{
"object": "block",
"type": "paragraph",
"paragraph": {
"rich_text": [{"type": "text", "text": {"content": content}}]
}
}]
)
Кэширование: обязательно для продакшена
Notion API имеет rate limit: примерно 3 запроса в секунду. При высоком трафике вы упрётесь в лимиты и данные будут загружаться медленно. Решение — кэширование.
Кэширование в Next.js (ISR — Incremental Static Regeneration)
// Next.js 14+ — кэш на 1 час
export async function getAllPosts() {
const response = await fetch(
`https://api.notion.com/v1/databases/${DATABASE_ID}/query`,
{
method: 'POST',
headers: {
'Authorization': `Bearer ${process.env.NOTION_TOKEN}`,
'Notion-Version': '2022-06-28',
'Content-Type': 'application/json'
},
next: { revalidate: 3600 } // Обновлять каждый час
}
)
return response.json()
}
Кэширование через Redis
import redis
import json
from notion_client import Client
r = redis.Redis(host='localhost', port=6379, db=0)
def get_posts_cached():
# Проверить кэш
cached = r.get('notion:posts')
if cached:
return json.loads(cached)
# Запросить из Notion
posts = get_published_posts()
# Сохранить в кэш на 1 час
r.setex('notion:posts', 3600, json.dumps(posts, ensure_ascii=False))
return posts
Webhook-инвалидация кэша
Для мгновенного обновления при изменении данных в Notion используйте Zapier или Make.com:
- Триггер: обновление страницы в Notion
- Действие: HTTP-запрос на ваш endpoint для очистки кэша
Ограничения Notion как базы данных
| Ограничение | Значение | Что означает | |---|---|---| | Rate limit | ~3 req/s | Нужен кэш для высокого трафика | | Размер базы данных | ~50 000 строк комфортно | Для большого каталога медленнее | | Вложенность блоков | Ограничена | Сложные страницы парсятся дольше | | Сортировка | По одному полю | Нет сложных ORDER BY | | Полнотекстовый поиск | Через API ограниченно | Нужна отдельная реализация | | Транзакции | Нет | Нельзя атомарно обновить несколько записей | | Бинарные данные | Файлы через CDN Notion | Ссылки временные (1 час) | | Автономность | Зависимость от Notion.so | Если Notion лежит — сайт не работает |
Когда Notion как БД — хороший выбор
- Блог или контент-сайт с несколькими сотнями статей
- Портфолио, где контент редко меняется
- Внутренние инструменты команды (дашборды, трекеры)
- MVP-проект, где главное — скорость запуска
- Продукт, где нетехнические люди должны редактировать контент без CMS
- Лендинги с динамическим контентом (FAQ, отзывы, команда)
Когда Notion — плохой выбор
- Высоконагруженные сайты (без серьёзного кэширования)
- Транзакционные системы (заказы, платежи)
- Приложения с пользовательскими данными (не храните персональные данные в Notion)
- Сложные поисковые сценарии
- Данные, которые нужно долгосрочно хранить без зависимости от SaaS
Инструменты для работы с Notion как БД
@notionhq/client— официальный JavaScript/TypeScript SDKnotion-client— Python SDKnotion-to-md— конвертация Notion-страниц в Markdownreact-notion-x— рендеринг Notion-страниц в React без API (читает публичные страницы)- Notaku — платформа для создания сайтов на основе Notion
- Super.so — кастомизируемые сайты из Notion
- Feather — блог-платформа поверх Notion
Notion для внутренней разработки (не публичный сайт)
Notion отлично подходит для внутренних инструментов команды:
- Трекер задач с дополнительными полями (бюджет, ответственный, дедлайн)
- CRM-подобная система для небольших команд
- Редакционный план для контент-маркетинга
- База знаний с поиском
- Дашборд проекта с rollup-вычислениями
Через Notion API можно автоматизировать:
# Автоматически создавать задачи при новых заказах
def create_task_from_order(order):
notion.pages.create(
parent={"database_id": TASKS_DB_ID},
properties={
"Название": {"title": [{"text": {"content": f"Обработать заказ #{order['id']}"}}]},
"Приоритет": {"select": {"name": "Высокий"}},
"Дедлайн": {"date": {"start": order['deadline']}},
"Статус": {"select": {"name": "Новая"}},
}
)
FAQ
Можно ли использовать Notion как основной backend для продакшн-приложения? Для content-driven сайтов — да, с кэшированием. Для приложений с большим числом пользователей, транзакциями или чувствительными данными — нет. Используйте PostgreSQL или другую реальную БД.
Как часто Notion меняет API?
Notion обновляет API относительно редко, но изменения бывают. Всегда указывайте версию в заголовке Notion-Version: 2022-06-28. Следите за changelog на developers.notion.com.
Бесплатен ли Notion API? API доступен на всех планах Notion, включая бесплатный. Но на бесплатном плане есть ограничения по числу участников и размеру загружаемых файлов.
Что лучше: Notion или Contentful для CMS? Contentful специализируется на Headless CMS и имеет более предсказуемый API, CDN, webhook-и из коробки. Notion удобнее для команды, которая уже работает в Notion. Для серьёзного контент-проекта — Contentful или Sanity. Для быстрого MVP или внутреннего инструмента — Notion.
Как защитить Notion Token?
Никогда не хардкодьте токен в коде. Используйте .env файл и переменные окружения. Никогда не выполняйте Notion API запросы на клиентской стороне (браузер) — токен будет виден всем. Все запросы — только через серверный код.