Alterna · онлайн-платформа турецкого A1–C1 по подписке
10 днейот $50 / мес
Документ v5
Веб-платформа турецкого по подписке: пять городов и 310 гайдов
План как в Duolingo, но материала больше, чем тестов. Каждый уровень — отдельный город. Каждая тема — гайд со своим дизайном. Дашборд показывает всё: от уровня ученика до секции, на которой он остановился. Только веб: телефон и компьютер в браузере.
310гайдов: 158 грамматика + 152 лексика
5городов, 960 часов, 5 000 слов
10дней до запуска MVP
Исходные требования
📚
310 гайдов с нуляграмматика и лексика, у каждого свой дизайн
Подробная аналитикауровень, до какой секции дочитан гайд, время
👥
1 000–5 000 в деньдо 10 000 аккаунтов, дальше рост
1
Свобода внутри, контракт снаружи
У каждого гайда свой дизайн и свои анимации. Но любой гайд одинаково размечает секции и упражнения и подключает один трекер. Платформе неважно, как гайд выглядит.
2
Минимум сервисов
Vercel, Supabase и Paddle закрывают хостинг, базу, вход, файлы и подписки. Ни одного сервера, который нужно администрировать. За 10 дней это решает.
3
Трекинг — часть продукта
Прогресс чтения ученик видит в дашборде, поэтому он хранится в своей базе. PostHog нужен команде для решений: воронки, удержание, конверсия из пробного в платный.
4
Карты — источник истины
Города, юниты и порядок уроков строятся из карт грамматики и лексики скриптом. Поменял карту — план пересобрался, код трогать не нужно.
Что уже есть. Карты грамматики (158 тем) и лексики (152 темы) со стабильными id, часами, пререквизитами и привязкой к городам. Это готовый скелет базы данных. Гайды делаются с нуля под контракт платформы. Язык пока один — турецкий, но в схеме есть course_id, поэтому второй язык потом добавляется без переделки.
Маршрут городов
Каждый уровень — город
Экран «План» — это карта маршрута. Ученик проходит уровень в одном городе: модули привязаны к его фокусу, в конце — «Остановка» с темой о самом городе и тест уровня, который открывает следующий город.
1 / 5A1
Стамбул
фразы, кафе, транспорт
Гайды
73 · 45 + 28
Часы
120
Словарь
750 слов
2 / 5A2
Измир
покупки, жильё, город
Гайды
60 · 30 + 30
Часы
120
Словарь
1 500 слов
3 / 5B1
Анталья
университет, аренда, работа
Гайды
61 · 27 + 34
Часы
235
Словарь
2 500 слов
4 / 5B2
Анкара
документы, собеседования
Гайды
49 · 21 + 28
Часы
225
Словарь
3 700 слов
5 / 5C1
Самсун
культура, дискуссии, свобода речи
Гайды
67 · 35 + 32
Часы
260
Словарь
5 000 слов
Гайды: грамматика + лексикаВсего: 310 гайдов · 960 часов · 5 000 словданные из карт грамматики и лексики A1–C1
Как устроен город
Прибытие
Только в Измире и АнкареБытовые темы под фокус города. Идут раньше нужной грамматики, готовыми фразами, и ничего не блокируют.
Юниты 1…N
Модули из карт, 5–7 уроков в юнитеКак Unit 1 и Unit 2 на макете.
📘 Гайд грамматики A1-20 · дательный падеж🍽️ Гайд лексики V-A1-18 · кафе✍️ Практика упражнения🎮 Игра слова юнита🎯 Тест юнита
Остановка
Финал уровняТема о самом городе: места, еда, история.
Билет в следующий городОриентирован на экзамен TÖMER, как и порядок тем в карте.
Построитель плана: карты → города и юниты
// scripts/build-plan.ts — запускается в CI,// когда меняется любая из картfor (const city of route) { // A1 Стамбул … C1 Самсун
plan.push(arrival(city)) // только A2 и B2// грамматика — по order, граф пререквизитовconst spine = topoSort(grammar[city.level])
// лексика встаёт после своей грамматикиfor (const v of vocab[city.level])
insertAfter(spine, v.recommended_after_grammar, v)
// режем на юниты по 5–7 уроков
plan.push(...chunk(spine, { min: 5, max: 7 })
.map(withPractice).map(withUnitTest))
plan.push(cityStop(city), tools(city), levelTest(city))
}
// результат → таблицы units и lessons в Postgres;// админ может вручную подвинуть урок (override)
Правила открытия уроков
жёстко
Грамматика
Тема открыта, когда пройдены её пререквизиты из карты. CI проверяет, что в графе нет циклов.
мягко
Лексика
Рекомендована после своей грамматики (recommended_after_grammar), но не блокируется.
всегда
Прибытие и повторение
Открыты сразу. Прибытие даётся готовыми фразами, повторение слов доступно в любой момент.
по тесту
Следующий город
Открывается тестом уровня. Тест при регистрации может сразу отправить ученика в нужный город.
Прогресс в часах, а не в процентах. У каждой темы в карте есть часы, у уровня — модель: уроки, повторение, тесты. Поэтому дашборд может сказать «до Измира ≈ 37 часов — около трёх месяцев при 25 минутах в день». Темы, у которых ещё нет гайда, показываются как «скоро» и не мешают проходить город.
Стек технологий · проверено 9 октября 2026
Технологии: итоговое решение
Выбор под три условия: только веб, платная подписка, 10 дней на одного разработчика с Claude Code. Версии — текущие стабильные в npm на сегодня, тарифы и ограничения сверены с документацией сервисов (источники — внизу раздела).
Решение
Next.js 16 + Supabase + Paddle на Vercel
Один язык, TypeScript, — от базы до гайдов
Три основных сервиса вместо десяти: меньше настроек и точек отказа
Ни одного сервера, который нужно администрировать
План Б для закона РК: тот же код в Docker на сервере в Казахстане
Слой
Выбор
Версия
Почему
Фреймворк
Next.js, App Router
16.4
Серверные компоненты, Server Actions, кэш через "use cache", proxy.ts для проверки входа. Лучшая поддержка на Vercel.
UI
React + TypeScript
19.3
Стандарт рынка. Claude Code пишет его лучше всего.
Стили и компоненты
Tailwind CSS + shadcn/ui
4.3CLI 4.21
Макет собирается из готовых компонентов, а их код лежит в твоём репозитории — меняешь как хочешь.
Анимации интерфейса
Motion
14
XP, стрики, переходы, карта городов.
Графики
Recharts
3.10
Дашборд ученика и аналитика в админке.
Формы и схемы
zod + react-hook-form
4.6
Одна схема проверяет форму, API и контент гайдов.
База, вход, файлы
Supabase: Postgres, Auth, Storage, pg_cron
js 2.117ssr 0.12
Всё бэкенд-ядро в одном сервисе, RLS из коробки. Переносится на свой сервер без переписывания кода.
Гайды и игры
HTML + CSS + GSAP + kit
GSAP 3.15
Полная свобода дизайна. GSAP со всеми плагинами бесплатен и в платных продуктах.
Повторение слов
ts-fsrs
5.4
Алгоритм FSRS, как в Anki.
Оплата
Paddle Billing
js 1.6node 3.10
Merchant of Record: подписки, повторные списания, налоги за тебя. Продавцы из Казахстана поддерживаются.
Письма
Resend + React Email
6.32
Письма с твоего домена, включая письма входа Supabase.
Озвучка
Azure Speech, голоса tr-TR Emel и Ahmet
—
Нейроголоса, SSML для медленной речи, около $15 за 1 млн символов.
Ошибки и аналитика
Sentry + PostHog
11.6
Ошибки, воронки, конверсия пробного в платный.
Тесты и линтер
Vitest, Playwright, Biome
5.01.64
Логика, сквозные сценарии на 375 px, один инструмент для линта и форматирования.
Хостинг
Vercel Pro (fra1) + Supabase Pro (Франкфурт)
—
Рядом друг с другом; бесплатный Hobby на Vercel запрещает коммерческое использование.
Что изменилось в 2025–2026 и важно не пропустить
Next.js 16: proxy.ts
middleware.ts переименован в proxy.ts и по умолчанию работает на Node.js. Кэш включается явно: cacheComponents: true и "use cache".
Supabase: новые ключи
Ключи sb_publishable_… и sb_secret_… вместо anon и service_role — старые отключают в конце 2026. Подпись JWT асимметричная, проверка через getClaims().
GSAP теперь бесплатный
С апреля 2025 все плагины бесплатны и для коммерческих проектов. Запрещено только делать на нём конструктор анимаций, конкурирующий с Webflow.
Эти детали прописаны в CLAUDE.md, чтобы Claude Code не писал код по устаревшим туториалам.
Почему не другие варианты
Vue / Nuxt
Нормальный фреймворк, но меньше готовых компонентов, а ИИ-ассистенты пишут его хуже, чем React.
Firebase
NoSQL: связи «ученик — тема — секция» и аналитика в нём тяжелее. На свой сервер в РК не перенести.
Stripe
Не работает с продавцами из Казахстана.
Свой сервер, Docker, Kubernetes
Администрирование съест несколько дней из десяти. Нужно только для плана Б.
Отдельный бэкенд на NestJS или Go
Второй проект, второй деплой. Next.js и Postgres-функции закрывают всё, что нужно.
Местный эквайринг сразу
Freedom Pay или TipTop Pay дешевле по комиссии, но подписки придётся писать самому. Это после запуска.
Источники
npm: next 16.4.0 и версии остальных пакетов — npm view, 9 октября 2026
Ученик работает в приложении. Гайд открывается внутри приложения во весь экран, как урок в Duolingo. Сервер отдаёт гайд только тем, у кого есть доступ. События чтения идут через приложение на сервер и в Postgres.
← Схему можно листать вбок →
Чтение. iframe запрашивает /g/a1.dative/v1 на том же домене. Сервер проверяет доступ и отдаёт HTML из приватного бакета. Без доступа — 403 и пейволл.
Трекинг. tracker.js сообщает приложению о секциях и ответах. Приложение добавляет пользователя и раз в 30 секунд отправляет пачку событий на /api/track.
Публикация. Новый гайд — мерж в репозиторий. CI проверяет, озвучивает, кладёт в Storage и синхронизирует каталог. Релиз приложения не нужен.
Фронтенд
HTML, React, Vue или что-то другое
Платформа и гайды решают разные задачи, поэтому одна технология на всё — ошибка. Платформа — веб-приложение с входом, подпиской, состоянием и данными. Гайд — длинная страница-история с тяжёлой анимацией, которую читают сверху вниз.
Платформа
React на Next.js 16
Быстрее всего с Claude Code. Нейросети лучше всего пишут React и Next.js — при сроке 10 дней это решает.
Всё для SaaS из коробки. Серверные компоненты, Server Actions, кэш каталога, proxy.ts для проверки входа.
Дизайн с макета — из готового. shadcn/ui, Motion, Recharts закрывают большую часть экранов.
Найм. React-разработчиков на рынке больше всего.
Гайды и игры
HTML + CSS + GSAP и общий kit
Без сборки. Гайд — один index.html. Claude Code генерирует такую страницу быстрее и надёжнее всего.
Общее не копируется. Упражнения, озвучка, карточки слов и трекер — web components из kit.js, подключаются одной строкой.
Один код упражнений. React 19 понимает web components, поэтому упражнение в гайде и в уроке «Практика» — одно и то же.
Быстро на слабом телефоне. Без фреймворка: только GSAP и код гайда.
Сравнение вариантов
Вариант
Для платформы
Для гайдов
React + Next.js
✓Экосистема, скорость с ИИ, всё для SaaS.
~Лишний рантайм, React и GSAP спорят за DOM.
HTML + GSAP + kit
✕Вход, подписка, роутинг и состояние пришлось бы писать руками.
✓Без сборки, полная свобода дизайна, общие части — из kit.
Vue + Nuxt
~Хороший фреймворк, но меньше готовых компонентов и хуже результат у ИИ-ассистентов.
~Те же минусы, что у React.
Astro
✕Не для приложения с личными данными.
~Хорош, но это ещё один шаг сборки. Можно перейти позже.
Чистый HTML без kit
✕—
✕Квиз, озвучка и трекер окажутся в 310 копиях.
Общие части
kit/kit.css
Цвета, шрифты, радиусы как CSS-переменные. Один источник для приложения, гайдов и игр.
приложениегайдыигры
kit/kit.js
Web components: <a-say> озвучка, <a-word> карточка, <a-exercise> упражнения из карты.
приложениегайды
kit/tracker.js
Без зависимостей: секции, активное время, ответы, озвучка → postMessage.
гайды
kit/game-sdk.js
Связь игры с платформой: получить слова, отправить очки, завершить игру.
игры
src/domain
Чистая логика: правила открытия, доступ, XP, часы до города, FSRS. С тестами Vitest.
приложениесервер
src/content/schema
Zod-схемы карт, guide.json, упражнений и game.json. Проверка в CI и типы в коде.
CIвсё
Экраны платформы
Лендинг и тарифыМаршрут (карта городов)Плеер гайдаПрактика и тестыДашбордПовторение словГрамматикаЛексикаРесурсыИгрыПодпискаНастройкиАдминкаСуперадмин
Экран
Как рендерится
Лендинг, тарифы
Статика — SEO и мгновенная загрузка
Маршрут и юниты
Server Components — данные сразу, без спиннеров
Плеер гайда, игры
Клиент + iframe, трекер через postMessage
Дашборд
Server Components из готовых агрегатов
Упражнения и тесты
Клиент: проверка мгновенно, XP начисляет сервер
Админка
Server Components + Server Actions, доступ по роли
Задача
Библиотека
Интерфейс
Tailwind CSS 4 + shadcn/ui
Данные с сервера
Server Components + Server Actions
Состояние плеера
Zustand
Формы
react-hook-form + zod
Карта городов
SVG + Motion
Графики дашборда
Recharts
Повторение слов
ts-fsrs
Устройства
От Android до ультраширокого монитора
Один код без отдельной мобильной версии. Верстаем сначала под самый узкий Android (360 px), потом добавляем место. Раскладку меняет ширина экрана, а не тип устройства: User-Agent не используем — iPad в Safari представляется как Mac.
до 767 px
Телефон
Android, iPhone
Одна колонка, нижняя панель из 5 вкладок, гайд во весь экран. Учитываем вырез и полоску «домой».
768–1023 px
Планшет вертикально
iPad, Android-планшеты
Панель иконок слева, карточки в две колонки.
1024–1599 px
Ноутбук и iPad горизонтально
MacBook, iPad landscape
Полный сайдбар, план в две колонки — как на макете.
1600–2559 px
Большой монитор
Full HD, 2K
Справа появляется колонка: цель дня, слова на повторение, стрик.
от 2560 px
Ультраширокий
3440×1440, 5K
Весь интерфейс по центру, не шире 2000 px. Текст не растягивается: строка до 75 знаков.
Правила вёрстки
Ширины — через max-width и clamp(), фиксированных ширин блоков нет.
Компоненты подстраиваются под свой контейнер (@container в Tailwind 4): карточка одинаково хорошо выглядит в сайдбаре и в центре.
Высота экрана — dvh вместо vh: адресная строка Safari и Chrome не обрезает контент.
Кнопки не меньше 44×44 px; hover — только для мыши: @media (hover: hover).
Шрифт в полях ввода не меньше 16 px — иначе iPhone приближает страницу.
Иконки — SVG, картинки — AVIF/WebP с версиями для Retina.
Гайд на разных экранах
Телефон: во весь экран, верхняя панель 48 px, кнопка «назад» Android закрывает гайд, а не уходит с платформы.
Ноутбук и iPad горизонтально: гайд не шире 1100 px по центру.
От 1600 px: слева оглавление секций с галочками, справа слова гайда.
Медиазапросы гайда смотрят на ширину iframe, поэтому гайд тоже верстается mobile-first.
При повороте экрана анимации GSAP пересчитываются; при prefers-reduced-motion показывается итог без анимации.
content:check снимает каждый гайд на 360, 768, 1280 и 1920 px.
Особенности платформ
iPhone · Safari
viewport-fit=cover и env(safe-area-inset-*) для выреза и полоски «домой». Звук — только по нажатию: автозапуск заблокирован.
Android · Chrome, Samsung
Кнопка «назад» закрывает плеер через историю браузера. Проверка на слабом телефоне с замедлением процессора ×4.
iPad
Safari выдаёт себя за Mac, поэтому различаем по (pointer: coarse). С клавиатурой и трекпадом работают hover и горячие клавиши. Поворот меняет раскладку без перезагрузки.
MacBook
Чёткие SVG и картинки 2x для Retina, hover-состояния, горячие клавиши в упражнениях: 1–4 — выбор, Enter — проверить.
Ультраширокий монитор
Контент и графики не растягиваются на всю ширину. Таблицы админки получают больше колонок, а не шире ячейки.
Как проверяем
Профиль Playwright
Движок
Размер
Что это
android
Chromium, касания
360×800
Бюджетный Android
iphone-se
WebKit, касания
375×667
Маленький iPhone
iphone
WebKit, касания
393×852
iPhone 15–17
iphone-max
WebKit, касания
430×932
Pro Max
ipad
WebKit, касания
820×1180 и 1180×820
iPad в двух ориентациях
macbook
Chromium, Retina ×2
1470×956
MacBook Air 13
fhd
Chromium
1920×1080
Обычный монитор
ultrawide
Chromium
3440×1440
Ультраширокий
На каждом профиле
нет горизонтального скролла
ничего не уходит под нижнюю панель и вырез
главные кнопки видны и нажимаются
скриншоты «Плана», плеера, дашборда и тарифов совпадают с эталоном
Lighthouse mobile на «Плане» и в плеере — не ниже 85
Поддерживаемые браузеры. Safari 16.4+ (iPhone 8 и новее), Chrome 111+, Firefox 128+, Samsung Internet, Edge — это требование Tailwind CSS 4. На iPhone 7 и старше (iOS 15) показываем плашку «Обновите браузер». Перед запуском главный сценарий проходится руками на дешёвом Android, iPhone, iPad и MacBook.
Гайды
310 уникальных страниц и один контракт
Гайды делаются с нуля под платформу, поэтому контракт закладывается с первого гайда: внутри — любой дизайн, снаружи — обязательные атрибуты. Id гайда = id темы из карты. Всё живёт в одном репозитории с платформой.
topic не найден в карте или уровень гайда не совпадает с уровнем темы;
секция в разметке не совпадает с guide.json или ключ повторяется;
на 375 px есть горизонтальный скролл или ошибки в консоли (Playwright);
JS + CSS гайда тяжелее бюджета — например, 400 КБ без медиа;
у упражнения нет темы и навыка — тогда дашборд не поймёт, что у ученика слабое.
Ключи секций — навсегда. Прогресс хранится по ключу, а не по номеру. Если потом вставить секцию в середину, у тех, кто уже читал гайд, прогресс не съедет.
Запрос идёт на тот же домен — cookie сессии уходит сама
Сервер проверяет доступ: пробный период или подписка
Читает HTML из приватного бакета Supabase Storage
Отдаёт гайд: кэш в браузере, встраивание только в платформу
Без доступа сервер отвечает 403, приложение показывает пейволл. Аудио и картинки лежат в публичном бакете с CDN: без HTML гайда они бесполезны, зато кэшируются. Когда трафик медиа превысит 250 ГБ в месяц, его можно перенести в Cloudflare R2 — меняется одна переменная.
Игры
Блок игр: добавлять новые без переделки платформы
Игра устроена как гайд: папка со статикой, свой game.json и общий SDK. Сама игра ничего не знает о турецком — слова и фразы ей передаёт платформа из тем, которые ученик уже прошёл. Одна механика сразу работает для всех пяти городов.
const game = await AlternaGame.init()
// game.content.words →// [{ tr: "elma", ru: "яблоко", audio: "…" }, …]// … игра идёт …
game.score(120)
game.complete({ correct: 18, total: 20 })
// платформа сохраняет game_session,// сервер начисляет XP с дневным лимитом
Как добавить игру
Папка public/games/<id>/ + game.json → мерж → Vercel выкладывает, CI регистрирует игру в каталоге. Через пару минут она в разделе «Игры». Позже — загрузка zip в суперадминке.
Безопасность
Игра запускается в iframe с sandbox="allow-scripts": у неё «чужое» происхождение, поэтому даже сторонний код не прочитает cookie и не сходит в API от имени ученика. Очки проверяет сервер, XP ограничен в день.
Что можно копировать
Механики («найди пару», wordle, виселица) не защищены — их можно брать. Чужой код, графику и названия — нельзя. Open-source игры с лицензией MIT или Apache можно адаптировать, сохранив лицензию.
Идеи первых игр
Найди пару: слово — переводСобери слово из буквАффикс на скорость: -da / -de / -ta / -teТурецкий wordleАудиодиктантСобери фразу из словВиселица
В 10 дней — SDK и одна игра («Найди пару»). Остальные добавляются папками после запуска.
Бэкенд и роли
Где лежат материалы и как текут данные
Главное правило: Git — источник истины для контента, Postgres — для учеников. Каталог в базе — только копия, которую перезаписывает CI. Поэтому любую правку контента можно проверить, откатить и найти в истории.
Отдаются только через сервер после проверки доступа. Каждая версия — своя папка, откат = вернуть номер версии.
Аудио и картинки
Supabase Storage, публичный бакет + CDN
Имя файла = хеш содержимого: без дублей, кэш навсегда. При росте — Cloudflare R2 без платы за трафик.
Kit, GSAP, шрифты, игры
Vercel CDN, папка public/
Деплоятся вместе с приложением, версия в пути: /kit/v1/.
Каталог: темы, план, секции, слова
Postgres (копия из Git)
Быстрые запросы и связи с прогрессом учеников.
Ученики, подписки, прогресс
Postgres
Транзакции, RLS, ежедневные бэкапы.
Путь гайда от темы до ученика
Тема из картыСтатус planned → ветка guide/a1.dative.
Гайд пишетсяClaude Code по шаблону → index.html, guide.json, упражнения.
Pull request → CIСхемы, контракт, Playwright на 375 px, бюджет веса.
ПревьюПроверяешь гайд по ссылке из PR.
Мерж → publishОзвучка, загрузка в guides/a1.dative/v1/.
content-syncКаталог → Postgres, статус published, сброс кэша. Гайд появился у учеников.
Шесть потоков данных
1 · Статика
Kit, игры, аудио, картинки — большая часть трафика. CDN, сервер не участвует.
2 · Гайды
HTML через сервер после проверки доступа, дальше — кэш в браузере.
3 · Каталог
План и темы одинаковы для всех. Кэш Next.js, сброс при публикации.
4 · Личное
Прогресс, дашборд, слова. Postgres по индексам, дашборд из агрегатов.
5 · События
~17 запросов/с в пик. Батчи, пакетная вставка, без дублей.
6 · Транзакции и фон
Урок пройден, XP, вебхуки Paddle, агрегаты, письма. Решает сервер.
Четыре роли
Пробный
Зарегистрировался, не платит. Доступ по правилам пробного периода.
После оплаты становится учеником автоматически, по вебхуку Paddle.
Ученик
Активная подписка: все города, гайды, упражнения, игры, дашборд.
Если оплата не прошла — Paddle повторяет списания, а доступ держится до конца периода.
Админ
Поддержка и контент: ученики, подписки, продление пробного, статусы тем, аналитика.
Не может назначать роли и менять цены.
Суперадмин
Ты, разработчик: всё, что может админ, плюс роли, цены, настройки, фича-флаги.
Назначается только SQL-миграцией, не из интерфейса.
Что можно
Пробный
Ученик
Админ
Суперадмин
Маршрут, первые уроки Стамбула, одна игра
✓
✓
✓
✓
Все города, упражнения, тесты, повторение, все игры
~по настройке
✓
✓
✓
Свой дашборд и статистика
✓
✓
✓
✓
Ученики, подписки, продление пробного, поддержка
✕
✕
✓
✓
Статусы тем, аналитика гайдов и игр
✕
✕
✓
✓
Назначать админов, цены, пробный период, фича-флаги
✕
✕
✕
✓
«Войти как ученик» (только чтение, с журналом)
✕
✕
✕
✓
Удалять аккаунты и данные
✕
✕
✕
✓
Как это устроено
Роль
app_role в профиле: user, admin, superadmin. Попадает в JWT через Custom Access Token Hook, RLS читает её оттуда.
Доступ
«Пробный» и «ученик» — не роль, а статус: trial, student, expired. Считается из trial_ends_at и подписки. Если бы это была роль, её пришлось бы переключать руками — и кто-то бы забыл.
Запись
Прогресс, XP и доступ меняет только сервер: /api/v1 и Postgres-функции. «Урок пройден → XP → стрик → следующий урок» — одна транзакция.
Защита
Для админа и суперадмина обязательна двухфакторная защита (TOTP в Supabase Auth). Каждое их действие пишется в audit_log.
RLS: ученик видит себя, админы — всех
alter table user_guide_progress enable row level security;
create policy"свой прогресс"on user_guide_progress for selectusing (user_id = (select auth.uid()));
create policy"админы видят всех"on user_guide_progress for selectusing (((select auth.jwt()) ->> 'app_role')
in ('admin', 'superadmin'));
-- политик insert/update нет:-- писать может только сервер (секретный ключ)-- суперадмин — только миграцией:update profiles set app_role = 'superadmin'where id = '<твой user id>';
Безопасность
RLS на каждой таблице; секретный ключ Supabase только на сервере и в CI
postMessage: проверка источника с обеих сторон
Гайды встраиваются только в платформу (frame-ancestors 'self')
Лимит запросов на /api/track и вход; подпись вебхуков Paddle проверяется
XP и прохождение считает сервер, клиент сообщает только факты
Ежедневные бэкапы; минимум личных данных; удаление аккаунта одной кнопкой
Хостинг и CI/CD
Где разместить платную платформу
Для подписочного продукта важны три вещи: тариф хостинга, который разрешает коммерческое использование, ежедневные бэкапы и платёжный провайдер, который работает с продавцами из твоей страны. Всё ниже — управляемые сервисы: серверы администрировать не нужно.
Компонент
Где
Тариф
Почему
Платформа
Vercel, регион fra1
Pro, $20
Бесплатный Hobby запрещает коммерческое использование. В Pro есть $20 кредита на использование, превью на каждый PR.
База, вход, файлы
Supabase, Франкфурт
Pro, от $25
Бесплатный тариф засыпает без активности и без бэкапов. В Pro — ежедневные бэкапы и 250 ГБ трафика.
Оплата
Paddle Billing
5% + $0,50 с платежа
Подписки, повторные списания, налоги и счета. Выплаты раз в месяц в USD или EUR.
Почта
Resend
$0–20
Письма с твоего домена: вход, пробный заканчивается, оплата.
Домен и DNS
Cloudflare
DNS бесплатно, домен ~$15–40 в год
Позже здесь же — R2 для медиа, если вырастет трафик.
Vercel и Supabase — в одном регионе. Функции Vercel ставятся в fra1, рядом с базой во Франкфурте. Если оставить регион по умолчанию (США), каждый запрос к базе станет медленнее на 100+ мс.
Нужны ли CI/CD и Docker
CI/CD
Да, но минимальный
Vercel сам деплоит: каждый push → превью-ссылка, main → прод. Настраивать нечего.
GitHub Actions, один файл. На PR — типы, линтер, Vitest, Playwright по превью. На main — миграции, публикация изменённых гайдов, content-sync.
Миграции БД — только из Git. Схему на проде руками не трогаем никогда.
Три окружения: локально, превью (отдельный проект Supabase), прод.
Docker
Нет — для деплоя не нужен
Деплой: Vercel и Supabase сами управляют серверами. Dockerfile писать не нужно.
Локально: Docker Desktop или OrbStack нужен только как движок для supabase start.
План Б: если юрист скажет хранить данные в Казахстане — VPS в РК (Yandex Cloud kz1, PS Cloud) и Docker Compose: Next.js + self-hosted Supabase. Это +3–5 дней работы.
Kubernetes: нет. При такой нагрузке это лишняя сложность.
.github/workflows/ci.yml — суть
on: [pull_request, push]
jobs:
check: # каждый PR
- pnpm typecheck && pnpm lint
- pnpm test # Vitest: domain, RLS
- pnpm guide:check --changed
- pnpm e2e --base-url=$PREVIEW_URL
release: # только main
- supabase db push # миграции
- pnpm content:publish --changed
# озвучка, гайды → Storage
- pnpm content:sync # каталог → Postgres
Мониторинг
Sentry: ошибки клиента и сервера, алерт в Telegram
Uptime-монитор на главную и /api/health — проверка раз в минуту
Логи Vercel и Supabase; отдельный алерт на ошибки /api/track и вебхуков Paddle
Секреты — в Vercel и GitHub Secrets; секретный ключ Supabase никогда не попадает в браузер
Трекинг
Как узнать, до какой части дочитан гайд
Гайд — отдельная страница в iframe, он не знает ни про ученика, ни про базу. Он только сообщает факты через postMessage, а приложение дописывает пользователя и отправляет данные на сервер. Поэтому 310 гайдов не зависят от кода платформы.
Секция в фокусеIntersectionObserver замечает секцию в центре экрана
Активное времятолько когда вкладка открыта и ученик что-то делал последние 30 с
postMessagetracker.js передаёт событие приложению
Батчраз в 30 с и при закрытии — через sendBeacon
/api/trackпроверка сессии, запись в events, обновление прогресса
Дашбордночью pg_cron собирает агрегаты для графиков
Когда секция считается прочитанной
Если засчитывать секцию при простом показе, быстрый скролл до конца даст «100% прочитано». Поэтому учитываем время:
секция в фокусеиактивное время ≥ 30% расчётногогде расчётное =data-words ÷ 150 слов в минуту
Гайд пройден, когда прочитано не меньше 80% секций и сдан тест в конце. Кнопка «Продолжить» на маршруте ведёт ровно в последнюю секцию.
Всё, что видит ученик: секции, время, ответы, стрики. Сырые события лежат в таблице с партициями по месяцам и живут 90 дней, агрегаты — всегда.
Слой 2 — PostHog
Только ключевые события: регистрация, первый гайд, пробный → оплата, город пройден, отмена подписки. Детальные события туда не шлём: при 5 000 учеников в день это десятки миллионов событий и сотни долларов в месяц.
Данные и дашборд
Что хранится и что из этого видно
Таблицы каталога заполняет CI из карт, гайдов и игр. Таблицы учеников пишет только сервер. Админка и суперадминка — свои экраны в приложении.
Ученик и доступ
profiles имя, часовой пояс, роль, конец пробного периода
subscriptions, payments копия статуса из Paddle, история оплат
settings длина пробного, что в нём доступно, id цен Paddle
Маршрут и контент
cities → units → lessons строит build-plan.ts
topics, topic_prereqs, topic_links 310 тем из карт, пререквизиты, связи
guides, guide_sections, exercises, vocab_items
Прогресс
user_lesson_progress закрыт, открыт, в процессе, пройден
user_guide_progress секции, %, время, лучший тест
exercise_attempts каждый ответ с темой и навыком
Слова
vocab_items слово, перевод, аудио, тема-источник
user_cards состояние FSRS: stability, difficulty, due
Игры и мотивация
games, game_sessions каталог игр, очки, время
xp_events, streaks за что XP, текущий и лучший стрик
Аналитика и аудит
events сырые, партиции по месяцам, 90 дней
daily_user_stats, guide_section_stats агрегаты и drop-off по секциям
audit_log все действия админов
Дашборд ученика
текущий город и часы до следующего
CEFR-уровень и уровень по каждому навыку
словарь: сколько слов из цели города уже выучено
каждый гайд: % прочитанного, где остановился, время, тест
минуты по дням, стрик, XP
слабые темы — по ошибкам в упражнениях
Аналитика в админке
drop-off по секциям каждого гайда: где закрывают
фактическое время на тему против часов из карты
самые трудные упражнения и вопросы
воронка: регистрация → первый гайд → оплата → Измир
конверсия пробного в платный, отток, MRR
карточка ученика: вся история по шагам
Как определяется уровень. При регистрации — адаптивный тест на 12–15 вопросов, он выбирает стартовый город. Дальше уровень растёт по пройденным городам, а уровни навыков считаются по ответам: у каждого упражнения есть навык и тема.
Нагрузка
Хватит ли мощности
Kit, игры и медиа раздаёт CDN, гайды кэшируются в браузере, каталог — в кэше Next.js. До базы доходит почти только личное: прогресс и события. Для Postgres это мало.
~500учеников онлайн в пик — около 10% от 5 000 в день
~17/сзапросов трекинга в пик при батче раз в 30 с
~5 млнсобытий в месяц, около 35 на ученика в день
×10запас: стек тянет 50 000 в день без переделки
Три слоя кэша
~95%
CDN и браузерKit, игры, аудио, картинки — кэш навсегда. HTML гайда — в браузере на час.
~4%
Кэш Next.jsМаршрут, темы, метаданные. Сбрасывается при публикации.
~1%
PostgresТолько личное: прогресс, события, подписка, слова на сегодня.
Доли — оценка по трафику. Пики: вечер 19:00–22:00, рассылки, начало учебного года. Соединения идут через пулер Supavisor, чтобы serverless-функции не исчерпали лимит базы.
Как расти дальше
до 10 000
Стек как есть: Supabase Pro с compute Small.
до 50 000
Compute Medium, реплика для чтения под дашборды, медиа — в Cloudflare R2.
100 000+
Сырые события → ClickHouse, очередь для событий и писем. Остальное не меняется.
Стоимость
Сколько всё стоит
Серверы — самая маленькая статья. Главные расходы: комиссия за оплату и время на контент. Цифры ориентировочные, по публичным тарифам на октябрь 2026 — проверь перед покупкой.
Продвигай годовой тариф. Фиксированные $0,50 съедают дешёвые месячные подписки. При 1 000 подписчиков сервер обходится примерно в $0,07 на ученика в месяц, а комиссия — в $0,90 с месячного платежа.
План на 10 дней
MVP за 10 дней с Claude Code
10 дней — реально для рабочей платформы, если работать полный день и не расширять объём. Это не 310 гайдов: конвейер гайдов стартует на 3-й день и идёт параллельно. Подробные промпты на каждый день — в плане для Claude Code.
Сделай сегодня, до дня 1. Подай заявку в Paddle: проверка домена и личности занимает несколько дней. Для проверки нужен живой сайт на твоём домене с ценами, офертой, политикой конфиденциальности и правилами возврата — поэтому лендинг с этими страницами выходит в первый же день.
Честно про контент. 310 уникальных гайдов с проверкой — это 10–15 в день, то есть ещё 3–4 недели после старта конвейера. К запуску реально: первые юниты Стамбула. Остальные темы видны на маршруте как «скоро» и открываются по мере публикации.
Готово, когда: все тесты зелёные, критичных замечаний ревью нет.
ДЕНЬ10
Запуск
Sonnet
Paddle в боевом режиме (после одобрения), прод-переменные
Юридические тексты от юриста на месте черновиков
Чек-лист запуска, бэкапы, алерты
Мягкий запуск на первых учеников
Готово, когда: первый настоящий ученик оплатил подписку.
∥ПараллельноС дня 3
Конвейер 310 гайдов
Город за городом, в порядке маршрута.
Стамбул 73Измир 60Анталья 61Анкара 49Самсун 67
Один промпт на гайд, шаблон и контракт уже в репозитории
Claude генерирует, ты проверяешь превью, CI проверяет контракт
10–15 гайдов в день → все 310 примерно за 3–4 недели
✕Не в 10 днейПосле запуска
Что сознательно откладываем
Всё это не мешает запуску и лучше делается на реальных данных.
Местный эквайринг (Freedom Pay или TipTop Pay) для оплаты в тенге
Оценка произношения и свободное письмо с проверкой ИИ
Остальные 7 типов упражнений и новые игры
Вход через Telegram, другие языки, ClickHouse
Claude Code
Как вести разработку с Claude Code
Два файла делают Claude Code предсказуемым: CLAUDE.md — правила проекта, которые он читает в каждой сессии, и PLAN.md — промпты на каждый из 10 дней. Положи оба в корень нового репозитория.
Ошибка в плане стоит дня работы. Opus лучше видит связи между частями.
Схема БД, RLS, роли, оплата, доступ к гайдам
Opus 5.5
high
Здесь деньги и безопасность — дешёвая модель тут обходится дороже.
Ревью безопасности (день 9), сложные баги
Opus 5.5
xhigh или max
Редко, но глубоко. Долго и дорого — только по делу.
Экраны, компоненты, API по готовому плану
Sonnet 5.5
medium
Основная масса кода. Быстро и экономно.
Трекер, построитель плана, движок упражнений
Sonnet 5.5
high
Логика с краевыми случаями, но задача хорошо описана.
Гайды, упражнения, игры по шаблону
Sonnet 5.5
medium
Много однотипной работы. Opus — только для эталонного первого гайда.
Тексты, мелкие правки стилей, переименования
Sonnet 5.5
low
Думать не над чем.
Ритм одного дня
Утро: план на OpusPlan mode (Shift+Tab), промпт дня из PLAN.md. Читаешь план, правишь, утверждаешь.
День: код на Sonnet/model → Sonnet. Одна задача — одна сессия; /clear между задачами, чтобы контекст не засорялся.
После каждой фичиТесты, проверка на 375 px, коммит. Превью-деплой на Vercel.
Вечер: ревью на Opus/code-review по изменениям дня, правки, мерж в main.
ПараллельноВо второй сессии Sonnet генерирует гайды по одному промпту на тему.
Решить до старта
Вопросы, которые меняют стек
Их нужно закрыть до дня 1 или в первые дни: от ответов зависит, где хранить данные, как принимать оплату и что видит пробный пользователь.
Где хранить данные учеников
Закон РК требует, чтобы база с персональными данными граждан находилась в Казахстане. Юристы спорят, допустима ли копия за рубежом, но основная база в РК устраивает оба толкования. С поправками 2026 года место хранения базы ещё и нужно указывать в уведомлении регулятору.
Решение для 10 дней: Supabase во Франкфурте, минимум личных данных (только email и имя) и консультация юриста сразу. Если нужна локализация — план Б: Docker Compose (Next.js + self-hosted Supabase) на сервере в РК, например Yandex Cloud kz1 или PS Cloud. Код не меняется, это +3–5 дней.
Как принимать оплату
Stripe не работает с продавцами из Казахстана. Paddle работает: Казахстана нет в списке запрещённых стран, НДС РК он считает и платит сам, выплаты раз в месяц в USD или EUR.
Решение: Paddle на старте, Polar — если Paddle откажет (Казахстан есть в списке выплат Polar). Местный эквайринг (Freedom Pay, TipTop Pay) — после запуска, когда выручка оправдает работу над своими подписками.
Как устроен пробный период
Варианты: 7 дней полного доступа без карты или первые уроки Стамбула навсегда.
Рекомендация: 7 дней полного доступа без карты (считает платформа, а не Paddle), после — остаются первые уроки Стамбула и пейволл. Всё хранится в settings, суперадмин меняет без релиза.
Цены и тарифы
Месяц и год, валюта, цена с НДС или без.
Из-за фиксированной комиссии $0,50 выгоднее продвигать годовой тариф. Цены живут в Paddle, в базе — только их id.
Способы входа
Email (код или ссылка) и Google — с первого дня.
Вход через Telegram популярен в СНГ, но в Supabase его нет из коробки. После запуска.
Бюджет скорости гайда
310 уникальных дизайнов без ограничений превратятся в 310 тяжёлых страниц на слабых телефонах.
Лимит 400 КБ JS + CSS без медиа и LCP меньше 2,5 с — проверка в CI с первого гайда.