Engineering

Why I Chose Next.js for Everything I Build

Feb 2, 2026
8 min read
E.A
Emmanuel Asika

From WordPress to Cloud Engineering: Why I went all-in on Next.js. A deep dive into Server Actions, TypeScript, and why the App Router is actually good.

I used to breathe PHP. I spent years in the WordPress trenches, building custom themes, wrestling with plugins, and managing servers that would crash if a post went viral. It paid the bills. It was fine.

But when I decided to shift my focus toward building scalable SaaS products and Indie Hacking, I needed a stack that moved as fast as I thought. I needed something that didn't just render HTML but managed the entire lifecycle of a user's interaction without feeling like a cobbled-together Frankenstein monster.

I looked at everything. Vue, raw React, Svelte, specialized frameworks like Remix. I tried them all.

I settled on Next.js. And honestly, I don't see myself changing that anytime soon.

This isn't a fanboy post. This is a pragmatic breakdown of why Next.js is the highest-leverage tool for a solo developer or a small team trying to ship fast. It's about the architecture, the developer experience, and the sheer speed from idea to production.

The Mental Model Shift: It's Just Modern PHP

Here is the hottest take I have: Next.js with the App Router is basically PHP for the modern era, but typed and componentized.

In the WordPress world, we rendered everything on the server. The browser got HTML. It was fast, SEO was automatic, and the mental model was simple. Then came the Single Page Application (SPA) craze. We started sending empty HTML shells and using massive JavaScript bundles to fetch data on the client. It was a mess. We had layout shifts, loading spinners everywhere, and Google hated it.

Next.js brought us back to sanity with Server Components.

With the App Router, I can write code that runs strictly on the server, fetches data directly from my database, and sends the finished HTML to the client. No exposing API keys. No massive client-side waterfalls.

Check this out. In a standard React SPA, fetching user data looks like a nightmare of useEffect, loading states, and error boundaries. In Next.js App Router, it looks like this:

import { createClient } from '@/utils/supabase/server'; export default async function Dashboard() { const supabase = createClient(); const { data: user } = await supabase.auth.getUser(); if (!user) { // Handle redirect logic here } const { data: projects } = await supabase .from('projects') .select('*') .where('user_id', user.id); return ( <main> <h1>Welcome back, {user.email}</h1> <ProjectList projects={projects} /> </main> ); }

That is it. It runs on the server. It waits for the data. It renders. This feels incredibly close to the PHP workflow I mastered years ago, but now I have the power of React interactivity where I need it. I get the best of both worlds without the context switching.

Server Actions: Killing the API Layer

As a Cloud Engineer, I love a good REST API. I love designing endpoints. But when I'm Indie Hacking? I hate them. They slow me down.

Writing a separate API route, defining the request body, handling the fetch on the frontend, serializing the data... it's friction. I just want to call a function to save data to the database.

Next.js Server Actions allow exactly that. I can write a function, export it, and call it directly from a form or a button component. Next.js handles the POST request, the serialization, and the security context behind the scenes.

Here is a real example from a SaaS boilerplate I'm building:

'use server' import { revalidatePath } from 'next/cache'; import { redirect } from 'next/navigation'; import { createClient } from '@/utils/supabase/server'; export async function createProject(formData: FormData) { const supabase = createClient(); const name = formData.get('name') as string; const { data: { user } } = await supabase.auth.getUser(); if (!user) return; await supabase.from('projects').insert({ name, user_id: user.id }); revalidatePath('/dashboard'); redirect('/dashboard'); }

I can pass this function strictly to a form action prop. No fetch('/api/projects'). No manual JSON parsing. It drastically reduces the amount of glue code I have to write. When you are a solo dev, glue code is the enemy. It is where bugs hide.

TypeScript is Non-Negotiable

I used to think loose typing was faster. I was wrong. Loose typing is faster for the first hour. It is slower for the next two years.

When I'm building complex cloud architectures or SaaS platforms, I need to know exactly what my data looks like. Next.js has first-class TypeScript support. It is baked in.

Combining this with Supabase is a superpower. I run a command to generate types from my database schema, and suddenly my entire frontend knows exactly what the database expects.

If I change a column name in my database, my build fails immediately. It tells me exactly where I need to update my code. This safety net allows me to refactor code aggressively without fear of breaking production. When I'm working late nights after my Masters' lectures, I don't have the mental capacity to remember if a field was named user_id or userId. TypeScript remembers for me.

The UI Stack: Tailwind + Shadcn

I am an engineer. I am not a designer. Give me a blank CSS file and I will stare at it for three hours.

For a long time, the solution was Bootstrap or Material UI. But those frameworks have a heavy opinion. You fight them to make your app look unique.

Then came Tailwind CSS, which gave us utility classes. It was faster, but you still had to build components from scratch.

Enter Shadcn UI. This isn't a component library you install as a dependency. It's a collection of reusable components built with Radix UI and Tailwind that you copy and paste into your project.

You own the code.

This distinction is critical. If I want to change how the Button component behaves, I just go to components/ui/button.tsx and change it. I'm not fighting an npm package version.

The combination of Next.js, Tailwind, and Shadcn is the standard for a reason. It allows me to build a dashboard that looks professional and trustworthy in a single afternoon. In the B2B SaaS space, design trust is everything. If your app looks broken, nobody cares how good your backend code is.

Deployment and the "Vendor Lock-in" Myth

People love to argue about Vercel lock-in. "Oh, Next.js is optimized for Vercel, you're trapped!"

So what?

I am studying Cloud Computing. I know how to spin up EC2 instances, configure Nginx, set up load balancers, and manage Docker containers. I can do all of that.

But I don't want to.

When I am wearing my Indie Hacker hat, infrastructure is a distraction. I want to git push and go to sleep. Vercel gives me that. It handles the edge caching, the image optimization, the serverless function cold starts (mostly), and the SSL certificates.

However, because I value freedom, I also know that Next.js creates a standalone build. I can create a Dockerfile, build the Next.js app, and deploy it to AWS ECS, Azure App Service, or a $5 Digital Ocean droplet if I really need to cut costs.

Here is what that Dockerfile looks like. It's standard:

FROM node:18-alpine AS base # Install dependencies only when needed FROM base AS deps WORKDIR /app COPY package.json yarn.lock* ./ RUN yarn install --frozen-lockfile # Rebuild the source code only when needed FROM base AS builder WORKDIR /app COPY --from=deps /app/node_modules ./node_modules COPY . . RUN yarn build # Production image, copy all the files and run next FROM base AS runner WORKDIR /app ENV NODE_ENV production COPY --from=builder /app/public ./public COPY --from=builder /app/.next/standalone ./ COPY --from=builder /app/.next/static ./.next/static EXPOSE 3000 ENV PORT 3000 CMD ["node", "server.js"]

The "lock-in" is soft. The convenience is hard to beat.

SEO and Performance

Coming from WordPress, I know how vital SEO is. You can't just build a SPA and hope Google's crawler executes your JavaScript correctly. It's getting better, but it's not perfect.

Next.js gives me server-side rendering (SSR) by default. The crawler sees the content immediately. This is non-negotiable for marketing pages, blogs (like this one), and public-facing directories.

Furthermore, the next/image component automatically optimizes images. It serves the right size, in the right format (WebP/AVIF), and prevents layout shifts (CLS). These Core Web Vitals are ranking factors. I don't have to install an image optimization plugin like I did in WordPress. It's just there.

The Ecosystem is Unmatched

When you pick a tech stack, you are picking a community.

If I have a problem with Next.js, someone has already solved it. There is a YouTube video, a Stack Overflow thread, or a GitHub discussion about it.

Integrations are seamless. Stripe? There's a Next.js example. Supabase? First-class support. OpenAI? The Vercel AI SDK is built specifically for Next.js streams.

Let's talk about AI for a second since that is where everyone is heading. The Vercel AI SDK makes streaming text responses from LLMs trivially easy in Next.js.

import { OpenAIStream, StreamingTextResponse } from 'ai'; import OpenAI from 'openai'; const openai = new OpenAI({ apiKey: process.env.OPENAI_API_KEY, }); export async function POST(req: Request) { const { messages } = await req.json(); const response = await openai.chat.completions.create({ model: 'gpt-4', stream: true, messages, }); const stream = OpenAIStream(response); return new StreamingTextResponse(stream); }

Try setting up a streaming response manually in a traditional Express server. It's doable, but it's more code. In Next.js, it's standardized.

But What About the Complexity?

Critics say Next.js has become too complex. They say the App Router is hard to learn.

I disagree. I think it forces you to understand web fundamentals. It forces you to understand what belongs on the server and what belongs on the client.

Yes, caching can be aggressive. Yes, you need to understand revalidatePath. But this complexity is the price of performance.

When I was building WordPress sites, complexity came from plugin conflicts and spaghetti PHP code. That was bad complexity. Next.js complexity is architectural complexity. It is manageable. It is logical. Once you understand the request lifecycle, it empowers you to build faster.

The Verdict

I am betting my career on the Cloud. I am betting my side projects on the ability to iterate quickly.

Next.js sits perfectly at the intersection of these two worlds. It respects the server (Cloud) and it respects the user experience (Frontend).

For anyone looking to move beyond simple websites and start building genuine web applications, this is the way. It allows me to operate as a full-stack engineer without feeling like I'm drowning in context switching.

I don't use Next.js because it's trendy. I use it because it lets me ship. And in this game, shipping is the only thing that counts.

#why#IndieHacker

Read Next