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
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
Why DeepSeek (not Claude or GPT-4)
Three reasons:
- Cost: ¥0.001 ≈ $0.00014 per 1K tokens. A full conversion costs ~¥0.01.
- Chinese developer friendly: API registration and billing work with Chinese payment methods. No Stripe requirement.
- 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" }
}
Key lessons learned:
JSON mode is non-negotiable. Without
response_format: json_object, the model occasionally adds explanatory text that breaks parsing.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."
Example-driven prompts beat instruction-only prompts. The v1 prompt was pure instructions. v2 included one example conversion. Output quality jumped significantly.
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)
}
}
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;
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;
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
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
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.
Token rotation before GitHub push. This is how I lost Cloudflare Pages. Rotate all tokens before the first commit, not after.
Supabase RLS from day one. Add GRANT permissions to your migration script immediately. Don't wait for the 500 error.
Monorepo > separate repos. Frontend, API, and database all in one Nuxt 3 project means one deploy command. For a solo dev, this is faster.
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)