← back to writing··5 min read

Why I'm rebuilding my Instagram automation on .NET and Angular

I shipped a working Instagram-stories scheduler on Next.js and Supabase. Now I'm rewriting it on ASP.NET Core 9 + Angular 19. Here's the honest comparison — what each stack gets right, and the four things that pushed me across the line.

I have a working Instagram-stories scheduler. It's a Next.js 16 app on top of Supabase, with Vitest + Playwright tests, a Vercel cron that fires every minute, and enough rate-limit handling to publish stories on schedule without getting throttled. It works. I'm replacing it with ASP.NET Core 9 and Angular 19.

This is not a “Next.js bad, .NET good” post. The old stack got real things right. The new stack costs more — more files, more concepts, longer compile times, more boilerplate per feature. The interesting question is what's worth that cost, and when. This is what pushed me across the line for this project.

What Next.js + Supabase got right

Three things, all of them load-bearing for shipping fast:

Auth + database in one tab. Supabase gives you Postgres, an auth service that hands out JWTs, and row-level security policies that let you write auth.uid() = user_id in SQL and call it done. I had real multi-tenant security in week one, not month three.

The deploy is the build. git push → Vercel rebuilds → preview URL appears in the PR comment. No infra to operate. No image registry. No cluster. The cost of shipping a tiny change is genuinely close to zero.

TypeScript end-to-end. One language across server actions, client components, Supabase types (generated from the schema), Vitest, and Playwright. Refactoring a column rename is a single TypeScript error away from being correctly propagated.

None of these are problems I'm trying to solve. I'm rebuilding because the problem changed.

Four things that pushed me across the line

1. The platform count went from one to six. The old app publishes to Instagram. The new one is a “short-form video dispatcher” — same input, six output platforms (IG Reels + Stories, TikTok, YouTube Shorts, Facebook Reels, X, Threads). Each platform has its own auth flow, rate-limit shape, retry semantics, and failure modes. The old app's structure — one route handler per concern, Supabase queries inline — would turn into spaghetti by platform three. I want explicit handlers, explicit pipelines, explicit failure boundaries.

2. Background jobs stopped being optional. Vercel Cron is fine for one job per minute. It's not fine for “fan out a single post into six platform-specific upload jobs, each with retries, exponential backoff, and a way to inspect the queue.” I could bolt that onto Next.js with Inngest or BullMQ, but at that point I'm running a queue and a web app on a runtime designed for one of them. .NET hosts long-running workers natively — BackgroundService, Channel<T>, IHostedService — without leaving the framework I'm already using for HTTP.

3. Strong typing across the wire, not just the language. Supabase's generated types are great inside one TypeScript codebase. They don't help when an external integration sends a payload your client wasn't generated against. .NET's OpenAPI tooling generates an API client for the Angular frontend from the same C# DTOs the backend returns. The contract is enforced at compile time on both sides of the wire. No runtime drift.

4. I want to learn the Microsoft enterprise stack. This is the honest one. Polish job postings ask for .NET, EF Core, Azure, and Angular at twice the rate they ask for the JAMstack. I'd been writing TypeScript exclusively for two years. Building something real on the Microsoft stack — not toy tutorials — is the fastest way to be credible in interviews for those roles.

What .NET + Angular gives you that's worth the cost

The cost is real. A four-layer Clean Architecture project (API, Application, Domain, Infrastructure) has more files than the same feature in Next.js. CQRS with MediatR means a “create post” endpoint is a command, a handler, a validator, and a DTO instead of a single app/api/posts/route.ts. The Angular side has standalone components, observables, and a router that takes a few hours to internalize.

What you get for that cost:

Boundaries that survive growth. When the create-post handler grows from 30 lines to 300, it's still a single file with a single responsibility. When you need to add a behavior — logging, validation, transactions — you write a IPipelineBehavior<TRequest, TResponse> once and it applies to every handler. The boundary doesn't decay as the system gets bigger. In the Next.js version, the same growth shows up as a 600-line route handler that nobody wants to touch.

Background work as a first-class citizen. A class that implements BackgroundService is hosted by the same process that serves HTTP, accesses the same DI container, uses the same EF Core context, logs through the same Serilog pipeline. There's no separate runtime to deploy or monitor. Schedulers, queue consumers, and the web API are siblings in one process.

A real type system on the frontend, again. Angular's HttpClient typed with the OpenAPI-generated client means every API call has correct request and response types without me writing them. When I rename a field in C#, regenerate the client, and the Angular component fails to compile in three places, that's the system working correctly.

Who shouldn't do this

If your app is one platform, two database tables, and a contact form: stay on Next.js. Vercel + Supabase will outpace any .NET stack on time-to-first-deploy by a wide margin, and the JAMstack ergonomics genuinely shrink with project size in the right direction.

If you're chasing roles where the team uses Next.js, stay on Next.js — depth in one stack beats a thin layer of .NET on your CV.

If your app's complexity is plateauing — same shape, more rows, more users, but not more kinds of things — there's no architecture upside to a rewrite, only a calendar cost. The .NET case lands when the system is becoming structurally different (more platforms, more job types, more contracts), not just bigger.

Where the rebuild is right now

Three commits in. Domain, Application, Infrastructure, and API projects scaffolded. Clean Architecture dependency rules enforced by project references. Docker compose for local SQL Server (Azure SQL Edge on ARM Macs) and Azurite for blob storage. CI workflow concurrency-controlled so PRs don't pile up. No handlers shipped yet.

I'll write the follow-up to this post when I've shipped the first three platform integrations and have something concrete to say about pipeline behaviors, FluentValidation in practice, and whether the OpenAPI client generator earns its place. The point of this post isn't “look how it turned out.” It's the part most rewrites skip — the decision to rewrite, and the case it has to make against the version that already works.