Перейти до вмісту
51studio
Туторіали

Postgres + Next.js: розумний стартовий стек

Автор: Sam Hollis12 хв читання

У кожному проєкті, який ми починаємо, перше рішення однакове: який стек?

Чесна відповідь для більшості проєктів — «нудний». Postgres для бази. Next.js для фреймворку. TypeScript наскрізно. Жменя рішень про те, як їх з'єднати. Нудний стек відвантажує швидко, нормально масштабується перші кілька років, і не воює з вами.

Цей пост — про те, що у тому стеку, і про рішення всередині нього. Це не туторіал. Це обґрунтування нудних виборів, щоб нудні вибори були свідомими, а не випадковими.

Компоненти

Дефолти, до яких ми тягнемось:

  • Next.js (App Router) — React, із серверними компонентами, server actions, кешуванням рівня маршруту.
  • Postgres — реляційна база, хоститься на Neon або Supabase.
  • Drizzle — TypeScript ORM і інструмент міграцій.
  • TypeScript — скрізь, strict mode, жодного any без виправдання.
  • Tailwind — utility-first CSS.
  • Vercel — хостинг.
  • Resend — транзакційний email.
  • Sanity — CMS, якщо у застосунку є контент поруч із даними.
  • Clerk або NextAuth — авторизація (див. пост про auth щодо вибору).

Цей стек покриває близько 80% проєктів, які ми відвантажуємо. Решта — винятки з причинами (комплаєнс, що вимагає self-hosting; реал-тайм-вимоги, що потребують специфічної інфри; масштаб-вимоги, що виправдовують managed-альтернативи Postgres).

Чому Postgres

Postgres — правильна відповідь для майже будь-якого проєкту, що потребує реляційної бази. Причини:

  • Він продакшн-рівня вже два десятиліття. Кейс-краї задокументовані.
  • Набір фіч щедрий: JSON-колонки, full-text пошук, materialised views, тригери, generated-колонки, row-level security. Не переростете.
  • Кожен managed-провайдер його підтримує. Neon, Supabase, RDS, Cloud SQL, Crunchy. Не залочені.
  • Екосистема навколо Postgres у TypeScript-світі зріла. Drizzle, Prisma, Kysely, raw SQL — усі хороші опції.

Випадки, коли Postgres не відповідь: коли у вас чітка NoSQL-форма (event logs, time-series, document-важкі схеми без джойнів) і команда має досвід з альтернативою. В іншому разі вибір Postgres за замовчуванням заощаджує рік на «треба було взяти Postgres».

Чому Neon або Supabase

Для хостингу Postgres ми рекомендуємо або Neon, або Supabase. Причини, чому не керуємо власним Postgres у проді:

  • Бекапи, failover, point-in-time recovery, патчинг: це повноцінні роботи, погано виконувані part-time DBA. Заплатіть комусь.
  • Ціна розумна. Для більшості застосунків $20-100/місяць покриває справжню базу з бекапами і пристойними лімітами з'єднань.
  • Обоє мають free-тариф, достатній для розробки і малих продакшн-навантажень.

Між ними:

  • Neon, якщо хочете serverless Postgres з гілками. База-на-pull-request — реальна фіча. Cold-start затримка тепер sub-second.
  • Supabase, якщо хочете Postgres плюс auth плюс storage плюс realtime у одному пакеті. Корисно, коли відвантажуєте швидко і не хочете складати чотири сервіси.

Ми за замовчуванням беремо Neon для більшості проєктів, бо вже використовуємо Clerk або NextAuth для авторизації, Sanity для контенту, і нам не потрібен пакет Supabase. Для проєктів, що стартують з нуля і хочуть одну консоль на все, Supabase — нормальний вибір.

Менеджмент з'єднань

Те, що кусає кожного, хто використовує Postgres із serverless Next.js: виснаження з'єднань.

Serverless-функції холодно стартують. Кожна хоче власне з'єднання з базою. Сплеск трафіку означає сотню одночасних функцій, що намагаються відкрити з'єднання. У Postgres є ліміти. Ви їх досягаєте, база відмовляє у нових з'єднаннях, сайт падає.

Рішення у порядку зростання користі:

  1. Singleton-клієнт у коді. Переконайтесь, що Postgres-клієнт переюзається між запитами всередині тієї ж теплої функції. Тривіально в App Router; складніше у pages-router serverless без специфічних паттернів.
  2. HTTP-базований драйвер. Serverless-драйвер Neon використовує HTTP під капотом, не raw TCP. Жодного пулу з'єднань для виснаження на клієнтській стороні. Це те, що ми використовуємо за замовчуванням.
  3. Connection pooler. І Neon, і Supabase пропонують пулер (PgBouncer). Сидить між вашими serverless-функціями і базою, мультиплексує з'єднання. Вмикайте. Безкоштовно.

Якщо ігноруєте менеджмент з'єднань до прода, проведете п'ятницю ввечері, дебагаючи це. Налаштуйте з першого дня.

Drizzle, Prisma чи raw SQL

Три розумні відповіді. Наш дефолт — Drizzle. Компроміси:

Drizzle — TypeScript ORM. Схема у TypeScript, запити, що виглядають як SQL з type inference. Міграції через SQL-файли або згенеровані. Легкий. Ми використовуємо на більшості нових проєктів.

Prisma — важчий, більше абстракції. Згенерований клієнт величезний. API запитів далі від SQL, що нормально для простих і обмежує для складних. Хороший, якщо команда його використовувала. Уникайте, якщо турбує cold-start розмір на serverless.

Raw SQL — найчесніший варіант. Використовуйте postgres.js або pg. Пишіть SQL-рядки, тегайте як template literals. Жодного ORM. Болісно для складних схем з багатьма таблицями. Чудово для проєктів, де схема мала і запити дивні.

Ми обираємо Drizzle, бо він дає 90% type safety Prisma за 10% ваги бандлу, і бо raw SQL припиняє масштабуватись приблизно на десяти таблицях.

Міграції

Три паттерни:

  1. Авто-згенеровані міграції зі змін схеми. І Drizzle, і Prisma це роблять. Редагуєте файл схеми, запускаєте команду, отримуєте файл міграції. Ревью. Коміт. Запуск.
  2. Власноруч написані файли міграцій. SQL-файли, нумеровані послідовно. Запуск по порядку. Old-school, але куленепробивне.
  3. Міграції рівня застосунку. Запуск на старті застосунку. Спокусливо; не робіть так у проді. Натрапите на race condition, коли кілька інстансів стартують одночасно.

Ми використовуємо гібрид: Drizzle генерує чернетку міграції, ми ревьюїмо і редагуємо перед комітом, і запускаємо міграції як крок деплою (не на старті застосунку). Для малих проєктів з одним інженером це загалом десять хвилин. Для більших крок ревью ловить те, що автогенерація хибить.

Server actions vs API routes

Next.js App Router дає два способи обробляти data-мутації з клієнта:

  • Server actions — викликаєте серверну функцію напряму з форми або клієнтського компонента. Type-safe наскрізно. Нове, переважно полірне, іноді дивне навколо обробки помилок.
  • API routes — пишете route handler за app/api/.... Старіший паттерн, більше boilerplate, цілком зрозумілий.

Ми використовуємо server actions для in-app мутацій (надсилання форми, правки, видалення), де caller — це наш власний UI. Використовуємо API routes для будь-чого, що є публічним API або таргетом webhook.

Server actions — не публічний API. Якщо ловите себе на думці «зовнішні клієнти мають викликати цю server action», ви зробили помилку. Перенесіть на API route з правильною авторизацією.

Наскрізна типобезпека

Обіцянка full-stack TypeScript — це що тип колонки у базі тече до типу даних у React-компоненті. З цим стеком воно переважно працює:

  1. Drizzle генерує типи зі схеми.
  2. Server actions повертають ці типи.
  3. Клієнтський компонент їх приймає.
  4. Наведіть на будь-яку змінну, щоб побачити inferred тип.

Місця, де це ламається:

  • JSON-колонки. Тип вмісту JSON — на вашій стороні. У Drizzle є хелпери; використовуйте.
  • Міграції, що змінюють типи колонок без оновлення consumers. Типи оновлюються; існуючий код може ні. Запускайте typecheck на кожному PR.
  • Скрізь, де as any або // @ts-ignore. Не робіть.

Система типів, що переважно правильна, але іноді неправильна — гірше за відсутність системи типів. Опирайтесь спокусі escape hatch. Виправляйте тип.

Ціна

Для реального продакшн-застосунку на цьому стеку розумна стартова ціна:

  • Vercel хостинг: $20/місяць за Pro-тариф, більше за вищий трафік.
  • Neon Postgres: $0-19/місяць для більшості проєктів, $69 для важчих навантажень.
  • Resend email: $20/місяць за перші 50K листів.
  • Sanity (якщо використовується): $0 для більшості маркетинг-обсягу контенту, масштабується з документами.
  • Clerk або NextAuth: $0 для NextAuth, $25/місяць для Clerk Pro.

Реалістично: $50-150/місяць для справжнього продакшн-застосунку у перший рік. Ціна масштабується з трафіком і розміром команди, не з використанням фіч.

Коли цей стек неправильний

Кілька випадків, де ми б тягнулись до іншого:

  • Реал-тайм скрізь. Liveblocks, PartyKit або кастомне WebSocket-налаштування виграють у polling на основі Postgres для справжнього real-time. Гібридний паттерн (Postgres для стану, WebSocket для live-оновлень) працює для більшості «майже-real-time» потреб.
  • Векторний пошук на масштабі. Postgres з pgvector справляється з малими embedding-навантаженнями. Для мільйонів векторів з low-latency пошуком — Pinecone або Qdrant.
  • Геопросторовість. Postgres із PostGIS — нормально. Якщо потрібна експертиза десяти інженерів у просторових запитах, наймайте її. Ми не вдаємо експертів тут.
  • Жорсткий комплаєнс. SOC 2 з on-prem вимогами штовхає вас з Vercel/Neon на власну інфраструктуру.

Для інших 80%: Postgres + Next.js + Drizzle + TypeScript — це стек, що відвантажує.

Якщо хочете веб-застосунок на цьому фундаменті, подивіться, як ми працюємо. Реальне ПЗ, спроєктоване від початку до кінця тими ж людьми, що його пишуть.

Схожі статті