We Published 118 Blog Posts With Zero Writers. Here's the Pipeline.
TL;DR Publishing surface is a REST endpoint, not a CMS. POST /api/outrank/webhook, bearer token, schema validated, revalidateTag() on write. Automate the mechanical, keep humans on judgment. Most content ops teams do this backwards: humans proofreading for length, machines picking topics. Ground drafts in your actual repo. A post that cites real file paths reads completely differently from a generic AI post. This is the whole trick. Allow the AI crawlers in robots.ts. GPTBot, Claude-Web, PerplexityBot. Honest caveat: the first three months of building this cost more than three months of hiring a writer. Applighter has published 118 blog posts, ranks for hundreds of long-tail queries, and does not employ a single content writer. This is not a growth-hack post. It's what SEO for developer tools actually looks like when you're a two-person team selling React Native templates and the marketing budget is whatever is left after Stripe fees. Why we couldn't just hire a writer Every "SEO for SaaS" guide assumes a content team, or at minimum a fractional writer at $500 a post. Selling templates at $99–$299, that math never worked. At 4 posts a month we'd need to sell one extra license per week just to break even on writers. That's before agency margins, before editorial overhead, and before the reality that most freelance writers cannot tell the difference between EAS Build and expo prebuild. The alternative most indie teams pick is "no blog," and then they lose to competitors who show up when a developer searches "supabase vs firebase for react native." So we rebuilt the operation around what we already had: engineers, a Next.js app, a Supabase database, and a Claude subscription. The swap-out table Function a content team does What we replaced it with Editorial calendar A markdown queue in scripts/blog-automation/blog-tracker.md Writer drafting a post Claude Code invoked with a /write-blog skill, --max-turns 25 Editor reviewing tone/facts House-style prompt with hard constraints plus a grep pass CMS entry A POST /api/outrank/webhook call with a bearer token SEO metadata handoff generateMetadata() in app/blog/[slug]/page.tsx reads from Supabase Cross-posting A Slack thread that pings a human to paste the pre-adapted version Analytics reporting Supabase view-count RPC plus a nightly query Nothing here is unusual on its own. What made it work was treating the pipeline like an internal API instead of a workflow. The endpoint is versioned. The tracker is in git. The skill prompt is code-reviewed. A blog post is a POST request, not a Notion doc that gets forgotten. Your publishing surface should be a REST endpoint No CMS. No admin panel. The single write path is POST /api/outrank/webhook, defined in app/api/outrank/webhook/route.ts, guarded by a bearer token, processed by modules/services/OutrankArticleService.ts. The service extracts author tags from the payload, dedupes by slug, and inserts into blog_posts. Immediately after the insert it calls: revalidateTag("blog-posts", { expire: 0 }); So Next.js drops the cached list within seconds instead of waiting out the TTL. Publishing looks like this from anywhere: curl -X POST https://yoursite.com/api/outrank/webhook \ -H "Authorization: Bearer $PUBLISH_TOKEN" \ -H "Content-Type: application/json" \ -d @post.json Three downstream benefits no CMS gives you: Anything can publish. A Claude Code agent, a curl from your laptop, a Zapier flow, a colleague's script. No proprietary editor to learn. Schema enforced at the boundary. Bad payloads get a 400. You cannot ship a post with a missing meta description, because the service rejects it before the row is written. Republishing is idempotent. The same slug hitting the endpoint twice updates the row rather than creating a duplicate. Failed publishes replay cleanly. A WordPress instance, a Ghost blog, a Contentful workspace: each is a second system to maintain, another set of credentials to leak, and a UI you have to teach a future collaborator. For a small studio the CMS is a liability, not an asset. Automate the mechanical, keep humans on judgment The most useful reframing we did was sorting every step into two buckets: mechanical (rules-based, reproducible) and judgmental (needs taste, opinion, or product context). Mechanical, fully automated: Slug generation from title Meta description length checks (under 160 chars) Frontmatter validation for Dev.to and Hashnode adaptations Image URL validation (Unsplash or Pexels only, no local /public/ paths) Internal link UTM parameter enforcement JSON-LD schema generation via components/json-ld.tsx (BlogPosting, FAQPage, VideoObject auto-extracted from embedded videos) Sitemap regeneration on publish, in app/sitemap.ts Judgmental, human-in-the-loop: Topic selection Whether a draft is on-brand Which templates get linked, with what anchor text Whether a claim in the post is defensible The mechanical bucket runs on a cron. The judgmental bucket happens over coffee. If you're an engineer this split is intuitive. It's how you'd design any pipeline where humans and machines share work. The mistake most content ops teams make is putting humans on the mechanical work (proofreading for length) and machines on the judgmental work (auto-generating topics from keyword volume). Reverse it. The trick that took us longest to figure out An AI-written post that references real file paths from your codebase reads dramatically differently from a generic AI-written post. The former sounds like it was written by someone who ships. The latter sounds like a content mill. Same model, same length, completely different reception. We enforce this by giving the /write-blog skill a research step that reads the actual repo before drafting. When a post claims "the OutrankArticleService dedupes by slug," it's because a subagent read modules/services/OutrankArticleService.ts and confirmed it. The house-style prompt has one hard rule: no "unlock the power of," no "in today's fast-paced world," no "game-changing solution." Not because those phrases are unprofessional, but because the audience is developers and developers spot LLM fluff instantly. If your first paragraph reads like a press release, the tab closes. The internal link graph is the product For a dev-tools company the internal link graph matters more than external backlinks, because the point of ranking for "React Native boilerplate" is routing that reader to a product page. Our rule: every post links to at least one template with UTM parameters, and anchor text varies per post so the pattern doesn't look manipulative. lib/blog-tags.ts maps every slug to topical tags, and getRelatedSlugs() surfaces three related posts at the bottom of each article. What's less common is treating the template catalog as a first-class node in that graph, not a destination sitting outside it. A post about Supabase auth links to the specific template that ships with that exact auth setup. Anchor text, product, and article all agree. Our top-converting page is a blog post about push notifications, not the homepage. Optimize for LLM crawlers, not just Google Open app/robots.ts and you'll find something that would have made a 2022 SEO agency nervous: we explicitly allow GPTBot, ChatGPT-User, Claude-Web, PerplexityBot, and Applebot-Extended. We block admin routes and auth callbacks. AI crawlers are welcome. The reasoning is boring math. When a developer asks ChatGPT or Claude for "best React Native template with Supabase auth," the answer comes from whatever the model has indexed. Being excluded from that retrieval set costs you a whole channel. Concretely it changes what we write: Explicit comparison tables that models can extract and quote FAQ sections with schema-marked Q/A pairs File paths and code fragments that are hard to hallucinate but easy to cite What we deliberately don't do No keyword-first planning. Picking a topic because it has 10,000 monthly searches is how you publish "What is TypeScript?" for the 400th time. We start with a real thing our templates do, then find the keyword that describes it. No translated backfill. Localizing 100 posts to 8 languages produces 800 pages of mostly-noise and wrecks your crawl budget. No featured-snippet chasing on non-transactional queries. A snippet for "what is a React component" is worth nothing when that reader isn't buying anything. No off-the-shelf content generation tools. We evaluated several. They produce content that ranks briefly then dies, because it has no anchor into a real product. Our pipeline works because it's coupled to our repo, not because it uses a fancier model. Steal this In the order we'd do it if starting over: Ship a /api/webhook publishing endpoint. Bearer auth, schema validation, revalidation on write. Two hours. Write a house-style prompt. Product terminology, repo file paths you want referenced, hard rules like "no emojis, no marketing verbs, no unfounded claims." One afternoon. Build a topic queue in git. One markdown file, one topic per line. Version-controlled, no vendor lock-in. Wire your model to write, then bash-post to your webhook. Skip every SaaS "AI blog platform." They exist to make you rent something you can build in 200 lines of shell. Do internal linking manually for the first 20 posts. You'll learn which anchor styles convert, then encode them into the prompt. Track conversions per post, not traffic per post. The honest caveat The AI pipeline sounds too good. Here's the part the case studies leave out: the first three months of building it cost more than three months of hiring a writer would have. We paid the setup cost because we were building once and running for years. If you're validating a product idea over the next 90 days, hire a writer. If you plan to still be publishing in three years, build the pipeline. What's your setup? If you're running content for a dev-tools company, drop your volume and stack in the comments. I'm most curious whether anyone has shrunk the judgmental bucket without the output going generic. That's the ceiling we keep hitting.
This is a summary aggregated from Dev.to. Read the complete article on the original site:
Read full article at Dev.to