Welcome to Topiqu AI Blog! 👋
A modern full-stack TypeScript blogging platform. Topiqu pairs a rich Tiptap-based editor with OpenAI-powered content generation and per-language article translation, real-time SSE notifications, multi-tenant client sites with custom domains, Stripe billing, and a robust permission layer authored in ZenStack.
- 📝 Rich Editor — Tiptap 3 with custom extensions (
Poll,slashCommand,indent), autosave, drafts, and quick release flow - 🤖 AI (OpenAI) — article generation, content enhancement, sentiment analysis, auto-image suggestions and LinkedIn post generator via
@ai-sdk/openai; token consumption metered perClientSiteplan - 🌍 Article translations — per-language
ArticleTranslationsidecar withOFF/MANUAL/AUTO/HYBRIDmodes; HYBRID holds machine translations atREADYfor human review before publishing. Language is a tab in the editor, listings and article pages resolve per locale, and realhreflang+ per-locale canonicals are emitted once a translation is published - 🔔 Realtime — Server-Sent Events notification system with an event publisher
- 🏢 Multi-tenant client sites — per-tenant subdomains, custom apex domains with DNS (CNAME) verification, plan-gated white-labeling
- 💳 Billing — Stripe subscriptions (
PRO/PREMIUM) + ad-hoc token top-ups, webhook-driven plan changes, monthly / annual cycles - 🔐 Auth & Security —
@sidebase/nuxt-auth, argon2 hashing, OTP / 2FA (otplib),nuxt-security, DOMPurify, zxcvbn password strength, FingerprintJS, content-checker profanity filter - 🛡️ Row-level access control — ZenStack policies in
schema.zmodelenforced server-side viagetEnhancedPrisma(user) - 🌐 i18n — Full Czech / English localization (
@nuxtjs/i18n); Czech-language route slugs - 🎨 Theming — UnoCSS utility-first, SCSS partials, persisted theme store, custom emojis & branding (plan-gated)
- 🔎 SEO —
@nuxtjs/seo, dynamic OG images via Takumi, sitemap, schema.org metadata, priority indexing & sourcing per plan - 📊 Analytics & Charts — Chart.js + vue-chartjs,
nuxt-gtag(GA4), GAM ad integration,allowAdsplan gate - 📄 PDF / OG rendering — no browser runtime: OG images via
@takumi-rs/*(Rust/WASM) +nuxt-og-image, PDF export viapdfkit - ⚡ Caching — Redis cache-aside (
server/utils/cache.ts) with generation-counter invalidation; degrades gracefully to no caching when unconfigured - 🩺 Observability —
@sentry/nuxterror tracking + session replay, ingested by Better Stack - 📧 Transactional Email — MJML templates rendered and sent via AWS SES
- ☁️ AWS — S3 storage (uploads), Rekognition (image moderation), SES (mail)
- ⏱️ Scheduled tasks — Nitro
tasks/for community insights, article generation and draining the translation queue (enableCrongate) - 📱 PWA — Installable, offline-capable via
@vite-pwa/nuxt - 🧩 Headless UI —
@headlessui/vue+@floating-ui/dom+ Tippy.js for accessible overlays, dropdowns, dialogs - ... and more
| Layer | Technology |
|---|---|
| Framework | Nuxt 4 (Vue 3.5, Composition API) |
| Language | TypeScript (strict) |
| Styling | UnoCSS + SCSS |
| i18n | @nuxtjs/i18n — en, cs |
| Auth | @sidebase/nuxt-auth + Prisma adapter |
| ORM / Schema | Prisma 6 + ZenStack v2 (access policy) |
| Database | PostgreSQL (via Docker Compose) |
| Editor | Tiptap 3 with custom extensions |
| AI | Vercel AI SDK + @ai-sdk/openai |
| Billing | Stripe via @unlok-co/nuxt-stripe (subscriptions + token top-ups) |
| SEO | @nuxtjs/seo, nuxt-og-image, nuxt-gtag |
| Cache | Redis — cache-aside + generation counters (server/utils/cache.ts) |
| Observability | @sentry/nuxt → Better Stack |
| Cloud | AWS (S3, Rekognition, SES), Stripe; self-hosted on a VPS via Dokploy (Docker Swarm + Traefik) |
| Testing | Vitest + @nuxt/test-utils (jsdom environment) |
| Tooling | ESLint, Prettier, vue-tsc |
app/ Nuxt app layer (pages, components, composables, stores, layouts)
server/ Nitro server (API routes, scheduled tasks, utils)
shared/ Cross-cutting code (zod schemas, utils) shared by app & server
prisma/ ZenStack source (schema.zmodel), generated Prisma schema, migrations
extensions/ Custom Tiptap editor extensions
emails/ MJML email templates
scripts/ Dev-only verification scripts (ai:smoke, db:seed-tokens)
i18n/ Locale files (en, cs)
types/ Ambient TS types
public/ Static assets
See MAP.md for the complete architecture reference — it is the single source of truth.
Requirements
- 📦 Bun (recommended) — Node-compatible package manager & runtime
- 🐳 Docker — used by
predevto spin up PostgreSQL - 🔑 A configured
.env— copy.env.exampleand fill it in (Auth, AWS, Stripe, AI keys, etc.)
Install & run in dev mode
bun install # installs deps + runs `nuxt prepare`
bun zenstack:generate # generates Prisma schema from schema.zmodel
bun prisma:deploy # applies migrations to the DB
bun dev # spins up Postgres via docker, starts Nuxt dev serverDatabase management
bun db:up # start Postgres container
bun db:down # stop Postgres container
bun db:reset # wipe volumes & restart fresh
bun db:fresh # reset + migrate + seed
bun db:seed # seed initial dataDev verification scripts — see MAP.md §1
bun ai:smoke # hit OpenAI directly with the ids in modelRegistry.ts (no DB, no dev server)
bun db:seed-tokens # top a local ClientSite up to 50k tokens so the generate-article cron picks it upBuild
bun run build # server build (zenstack generate + prisma deploy + nuxt build)
bun build:docker # container build (skips prisma deploy — see Dockerfile)
bun generate # static site generation
bun preview # preview production buildRun bun zenstack:generate first on a fresh checkout — part of the suite imports the generated
shared/zod models, and those tests fail to load without it. No database is required.
bun test # run vitest once
bun test:watch # watch mode
bun test:coverage # coverage report (v8)
bun typecheck # vue-tsc strict typecheckESLint (@nuxt/eslint) + Prettier are enforced project-wide.
bun lint # check
bun lint:fix # auto-fix
bun prettier # check formatting
bun prettier:fix # apply formatting
bun fmt # lint:fix + prettier:fix- Follow the rules in
CLAUDE.md— Staff-Level Nuxt 4 / Vue 3.5 standards. - Every structural change must be reflected in
MAP.md. - All user-facing strings must use
$t('key')— no raw text in templates. - Prefer Vue 3.5 macros (
defineModel,useTemplateRef, …) and VueUse composables over manualref()/reactive(). - Vue SFC order:
<template>first, then<script setup lang="ts">, then<style>(avoid<style>blocks whenever possible — prefer pure UnoCSS). - Server API: prefer the ZenStack-enhanced client (
getEnhancedPrisma(user)) over rawprismaso access policies fromschema.zmodelare enforced — fall back to rawprismaonly when policies must be bypassed intentionally. - Error responses: use
createError({ statusCode, message })with i18n keys viauseServerI18nwhere applicable. - Naming & comments: name things after what they literally are (mirror the domain vocabulary), skip ceremony words (
enforce…Invariant,…Handler,…Manager), and keep a comment only when it says something the code cannot. Architectural prose belongs inMAP.md. - Don't "modernize" without reason — keep changes terse and scoped to the task; refactors require explicit approval.
- Significant changes should be covered by Vitest tests.
Made with ❤️ by @bigjohnn1 & @germondai