DEV Community

Pizza Cat
Pizza Cat

Posted on

Building an AI-Powered SaaS with Nuxt 3 + Supabase: Full Architecture Breakdown

Last week I built an AI-powered content repurposing tool called MultiPost. You write one article, it generates platform-optimized versions for Twitter, LinkedIn, Dev.to, Reddit, and Indie Hackers.

The product works. The AI engine cost ~¥0.01 per conversion. The whole thing went from zero to deployed in ~48 hours.

But this post isn't about the product. It's about how it works under the hood — the architecture decisions, the AI prompt design, and the deployment tricks I learned along the way.


The Architecture at a Glance

Here's the full stack:

Frontend (Nuxt 3 SSR)
  ├── / → Landing Page
  ├── /convert → AI Conversion UI
  ├── /dashboard → Conversion History
  └── /settings → Account + Dev.to Integration

Server (Nuxt + H3)
  ├── /api/convert → DeepSeek AI Conversion
  ├── /api/history → Supabase CRUD
  ├── /api/devto → Dev.to Publish Proxy
  └── /api/auth → Supabase Auth Helpers

Database (Supabase)
  ├── profiles → User profiles
  ├── conversions → AI conversion history
  └── usage → Usage tracking

Deployment (Netlify)
  └── CNAME → multipost.clb520.ccwu.cc
Enter fullscreen mode Exit fullscreen mode

Key decision: I used Nuxt 3 server routes (H3) for all API endpoints — no separate backend. This means frontend and API are deployed as a single artifact. For a solo dev building a Micro-SaaS, this is a massive productivity win.


The AI Engine: Prompt Design That Actually Works

The heart of the app is the conversion engine. Here's the flow:

Input Article (markdown text)
  → DeepSeek Chat API (single prompt, no chain)
  → JSON Response with 5 platform variants
  → Parse → Validate → Display in Tabs
  → User Edits → Copy or Publish
Enter fullscreen mode Exit fullscreen mode

Why DeepSeek (not Claude or GPT-4)

Three reasons:

  1. Cost: ¥0.001 ≈ $0.00014 per 1K tokens. A full conversion costs ~¥0.01.
  2. Chinese developer friendly: API registration and billing work with Chinese payment methods. No Stripe requirement.
  3. Quality: For content rewriting (not creative generation), DeepSeek matches GPT-4 at 1/30th the cost.

At $0.0014 per conversion, even at 1000 conversions/month, the AI cost is $1.40. This makes the $19/month pricing viable with 90%+ margins.

The Prompt Architecture

Single-prompt design, not chain-of-thought:

{
  "system": "You are a content repurposing expert...",
  "messages": [
    {
      "role": "user",
      "content": "Convert this article for 5 platforms.\n\nArticle:\n{input}\n\nReturn JSON: { twitter: { tweets: [string] }, linkedin: { text: string }, devto: { markdown: string }, reddit: { text: string }, indiehackers: { text: string } }"
    }
  ],
  "response_format": { "type": "json_object" }
}
Enter fullscreen mode Exit fullscreen mode

Key lessons learned:

  1. JSON mode is non-negotiable. Without response_format: json_object, the model occasionally adds explanatory text that breaks parsing.

  2. Platform-specific context matters more than word count limits. Telling the model "Twitter wants a hook in every tweet" produces better results than "write 7 tweets under 280 chars."

  3. Example-driven prompts beat instruction-only prompts. The v1 prompt was pure instructions. v2 included one example conversion. Output quality jumped significantly.

  4. Temperature = 0.7 for creative rewrites, 0.3 for technical content. Technical articles stay closer to the source. Marketing content gets more variation.

Robust Parsing

DeepSeek's JSON mode is good but not perfect. Occasionally you get:

  • Escape character issues in code blocks
  • Truncated responses (token limit hit)
  • Missing keys in the JSON

The fix: a safeParse() function that validates each platform variant and falls back to the raw input for any that fail:

function safeParse(json: string): ConversionResult {
  try {
    const parsed = JSON.parse(json)
    const platforms = ['twitter', 'linkedin', 'devto', 'reddit', 'indiehackers']
    const result = {}
    for (const platform of platforms) {
      result[platform] = parsed[platform] ?? { text: rawInput }
    }
    return result
  } catch {
    return fallbackToRaw(input)
  }
}
Enter fullscreen mode Exit fullscreen mode

Supabase: Database + Auth in One

Supabase does double duty — authentication and data storage. Here's the schema:

Tables

-- Profiles (auto-created on signup)
create table profiles (
  id uuid references auth.users primary key,
  email text,
  full_name text,
  created_at timestamp default now()
);

-- Conversion history
create table conversions (
  id uuid default gen_random_uuid() primary key,
  user_id uuid references profiles(id),
  input_title text,
  input_content text,
  output jsonb,  -- stores all 5 platform versions
  created_at timestamp default now()
);

-- Usage tracking
create table usage (
  user_id uuid references profiles(id) primary key,
  conversion_count int default 0,
  period_start timestamp default now()
);

-- RPC for atomic increment
create or replace function increment_usage(user_id uuid)
returns int as $$
  update usage
  set conversion_count = conversion_count + 1
  where id = user_id
  returning conversion_count;
$$ language sql;
Enter fullscreen mode Exit fullscreen mode

RLS Gotcha

The biggest Supabase issue I hit: RLS without explicit GRANTs causes silent 500 errors.

For anon role (pre-auth), you need:

GRANT USAGE ON SCHEMA public TO anon;
GRANT ALL ON ALL TABLES IN SCHEMA public TO authenticated;
Enter fullscreen mode Exit fullscreen mode

Without this, Supabase returns a generic 500 error that tells you nothing useful. I spent an hour debugging this.


Deployment: The Cloudflare Lesson

Originally I targeted Cloudflare Pages. Then my Pages got blocked due to an API token leak (rotated immediately, but Cloudflare's abuse detection flagged it).

The workaround: Netlify.

Deployment flow:

npm run build
npx netlify deploy --prod --dir=.output/public
Enter fullscreen mode Exit fullscreen mode

Netlify handles Nuxt 3 SSR out of the box. Server routes work without any adapter configuration.

Then I pointed multipost.clb520.ccwu.cc at Netlify via a CNAME record. The trick with SSL was: turn off Cloudflare proxy → let Netlify auto-provision Let's Encrypt → turn proxy back on.


What I'd Do Differently

  1. Start with a landing page, not the full product. I did this ✓. The landing page validated the idea before I wrote a line of product code.

  2. Token rotation before GitHub push. This is how I lost Cloudflare Pages. Rotate all tokens before the first commit, not after.

  3. Supabase RLS from day one. Add GRANT permissions to your migration script immediately. Don't wait for the 500 error.

  4. Monorepo > separate repos. Frontend, API, and database all in one Nuxt 3 project means one deploy command. For a solo dev, this is faster.

  5. Price higher. I started at $19/month. After seeing what people actually pay for tools like Content Snare ($29) and Senja ($39), $19 feels low.


The Numbers

Metric Value
Time to first deploy ~48 hours
AI model DeepSeek Chat
AI cost per conversion ¥0.01 (~$0.0014)
Infrastructure cost/month $0 (Netlify free + Supabase free)
Code size ~800 lines Vue, ~200 lines API
Database Supabase (3 tables, 1 RPC)

If you're building a Nuxt 3 SaaS and have questions about the architecture, AI integration, or deployment — drop a comment. I'll share whatever I can.

And if you want to try MultiPost for your own content distribution: multipost.clb520.ccwu.cc — free tier available, no credit card needed.


Built with Nuxt 3, Supabase, DeepSeek. Deployed on Netlify. Written by a solo dev who hates copy-pasting.

Top comments (0)