Heath SchweitzerHeath Schweitzer
← All posts

From LAMP to the Modern Web: Rebuilding My Stack After 20 Years

April 4, 2026|Heath Schweitzer|7 min read|17 views|Last Updated June 20, 2026

LinuxTechnology
Illustration showing a transition from the LAMP stack to a modern Next.js-based web stack with a 20-year timeline and decision path.

For more than two decades, I built websites and web applications on the LAMP stack. Linux, Apache, MySQL, PHP. Later Nginx replaced Apache for me, and WordPress became a significant part of my work, but the core remained the same. It worked. It worked reliably, it worked at scale, and I knew it deeply.

So why change?

Partially fear. The quiet, nagging kind that settles in when you look at job postings and notice the technologies you know best appearing less frequently, and the ones you don't know appearing everywhere. I'd been watching the JavaScript ecosystem mature from a distance for years, telling myself it was too fragmented, too fast-moving, too different. At some point that stopped being an observation and started being an excuse.

I decided it was time to close the gap.

Why This Stack

My goal wasn't just to learn React or TypeScript in isolation. I wanted to understand the modern full-stack approach that's increasingly aligned with how AI agents and agentic workflows are built. After research I landed on a stack centered around Next.js — sometimes called the T3-adjacent stack by the community: Next.js, TypeScript, Prisma, Tailwind CSS, and shadcn/ui.

The reasons this stack made sense for me specifically:

Next.js handles both the frontend and backend in a single framework. For a LAMP developer this is actually familiar territory — PHP was always both a templating language and a server-side runtime. Next.js brings that same "one framework, full stack" philosophy to the JavaScript world, just with a very different mental model underneath.

Prisma replaces raw SQL and PDO. I've written tens of thousands of MySQL queries over the years. Prisma gives me a type-safe query builder that generates from a schema file — closer to Laravel's Eloquent than anything I'd used in the PHP world, but with TypeScript autocomplete that catches errors before they reach the database.

Tailwind CSS replaces custom stylesheets. Instead of writing CSS classes and maintaining separate files, you compose utility classes directly in your markup. It sounds chaotic until you use it — then it starts to feel like the way CSS should have worked all along.

shadcn/ui gives you a library of accessible, production-quality components that you actually own. You paste them into your project and modify them freely. No black-box npm package to fight with.

NextAuth.js handles authentication — sessions, JWT tokens, OAuth providers — with minimal configuration. The kind of auth system that would have taken me days to build securely in PHP is configured in a single file.

The Project: Rebuilding heathschweitzer.com

Rather than building a toy app, I rebuilt this site — my actual personal site and blog, which had been running on WordPress for over a decade. That decision turned out to be the right call. Having real content, real constraints, and real stakes made every lesson stick in a way that tutorial projects never do.

The app I ended up building includes:

  • A personal landing page
  • A full blog with category filtering, pagination, and Markdown rendering
  • A WordPress content importer with selective keep/skip per post
  • An admin dashboard with post creation, editing, and deletion
  • Authentication protecting the admin routes
  • Transactional email via Resend
  • Production deployment on CloudPanel with PM2 and Nginx

The Mental Model Shifts

This is the part no tutorial adequately prepares you for. The syntax of TypeScript and JSX is learnable in a weekend. The mental model shifts take longer.

Server Components vs Client Components is the biggest one. In PHP, every page request runs your code on the server and returns HTML. In Next.js with the App Router, this is still true for Server Components — they run on the server, fetch data directly, and return rendered HTML. But Client Components run in the browser and handle interactivity. Learning to think about which category each piece of your UI belongs to, and why, is the core skill of modern Next.js development.

A concrete example from this project: the admin dashboard fetches all posts on the server (a Server Component), but the checkboxes, delete confirmations, and bulk actions need browser interactivity — so that piece is a separate Client Component that receives the posts as props. The server does the data fetching. The client handles the interaction. Clean separation.

File-based routing replaces your web server configuration. In Apache or Nginx you write rewrite rules. In Next.js you create folders and files. A file at app/blog/[slug]/page.tsx automatically handles every URL matching /blog/anything. The [slug] in brackets tells Next.js that segment is a variable — your LAMP equivalent would be WHERE slug = $_GET['slug'] but the routing itself is declared through folder structure rather than configuration.

The ORM replaces SQL in your daily workflow. I still know what SQL is being generated — Prisma is not magic — but I stopped writing it by hand. Instead I define my schema in prisma/schema.prisma, run a migration command, and get a fully typed client. The many-to-many relationship between posts and categories that would have required a join table and careful JOIN queries in SQL is handled transparently. I just write post.categories and Prisma handles the rest.

Environment variables have a proper home. In WordPress everything sensitive went in wp-config.php. In Next.js everything goes in .env.local, which is gitignored by default. Variables prefixed with NEXT_PUBLIC_ are exposed to the browser; everything else stays server-only. It's a small thing but it's the right default.

The WordPress Migration

One of the most satisfying parts of this project was building a selective WordPress importer. Rather than migrating every post blindly, I wrote a script that reads the WordPress XML export and presents each post one at a time in the terminal — title, date, category, excerpt — and lets you press k to keep or s to skip.

Kept posts are converted from WordPress's stored HTML to Markdown using the turndown library, then inserted into the new database with their original slugs, dates, categories, and tags preserved. The whole import is safe to run multiple times — a wpId field on each post tracks what came from WordPress and prevents duplicates.

35 posts from over a decade of writing, selectively curated and cleanly migrated in about 20 minutes.

Deployment

The production setup runs on a CloudPanel server I already had. CloudPanel manages Nginx, SSL, and site configuration through a web UI — similar in spirit to cPanel but more developer-friendly. For Node.js sites it generates an Nginx reverse proxy configuration automatically, pointing external traffic to whatever port your Node process is running on.

PM2 manages the Node.js process — keeping it alive, restarting it on crashes, and starting it automatically on server reboots. The deployment workflow is a single shell script:

git pull
npm install
npx prisma migrate deploy
npm run build
pm2 restart heathschweitzer

Pull the latest code, install any new dependencies, apply any pending database migrations, build the Next.js app, restart the process. The whole thing runs in under 30 seconds.

What Surprised Me

The speed of the feedback loop. With npm run dev running, changes to any file trigger an instant hot reload in the browser. No page refresh, no waiting, no losing your place in the UI. After years of saving a PHP file and hitting F5, this feels almost unreasonably fast.

The quality of the error messages. When something goes wrong in Next.js development, the error overlay shows you the exact file, line, and often a suggestion for how to fix it. TypeScript catches entire categories of bugs before you even run the code. I've spent less time debugging in this project than in any PHP project of comparable scope.

The ecosystem maturity. I was expecting the JavaScript ecosystem to feel chaotic — and it can be, if you go looking for chaos. But the combination of Next.js, Prisma, and NextAuth represents a genuinely mature, well-documented, well-maintained stack. The decisions about what to use had largely been made by the community already. I didn't have to figure out which of seventeen competing solutions to pick for each problem.

Where This Goes

This post is the first in what I'm planning as an ongoing series documenting the build as it evolves. Next up: adding AI-assisted content generation to the post editor, building a category management UI, and a design polish pass across all pages.

If you're a LAMP developer sitting on the fence about making this jump — I hope this is useful. The stack is different enough that it requires genuine relearning, but familiar enough that your existing instincts about databases, server-side rendering, and clean architecture transfer directly. The learning curve is real but shorter than I expected, and the tooling on the other side is genuinely better in ways that matter day to day.

Tagged

next.jsprismawordpress-migrationPM2Node.jsweb developmenttypescript

If this post was useful, consider buying me a coffee ☕ with ₿itcoin — no account needed, any amount welcome.

Bitcoin tip QR code
⚡ Open in Wallet