Web Development

From WordPress to Custom React: How I Cut Load Time by 70%

I used to run my blog on WordPress. It was easy to set up, had a thousand plugins, and my mom could probably figure out the dashboard.

Md. Rony Ahmed · 10 min read
From WordPress to Custom React: How I Cut Load Time by 70%

From WordPress to Custom React: How I Cut Load Time by 70%



I used to run my blog on WordPress. It was easy to set up, had a thousand plugins, and my mom could probably figure out the dashboard.

It also took 4.2 seconds to load the homepage. On a fast connection.

The final straw wasn't the speed — it was a plugin update that broke my contact form for three days before I noticed. A potential client filled out the form, got a 500 error, and moved on. I lost a $300 project to a PHP fatal error I didn't even know existed.

That's when I decided to rebuild everything from scratch. Not because I'm allergic to WordPress, but because I needed to know exactly what every line of code did.

The new stack loads in 0.8 seconds. Here's the migration and what I learned.




The Old Stack (And Why It Failed Me)



WordPress + Elementor + Shared Hosting

MetricBefore
Homepage load4.2s
Time to Interactive6.1s
PageSpeed score42/100
Plugin count23
Database queries per page187
Monthly cost$15

The problems weren't theoretical:

Plugin roulette. Every update was a coin flip. Will this break my navigation? Will Elementor's latest patch conflict with my caching plugin? I spent more time troubleshooting WordPress than writing.

Shared hosting sharing. My "unlimited" plan meant I was on a server with 400 other websites. One of them got a traffic spike and my database queries started timing out.

Bloated pages. Elementor is powerful, but it generates DOM trees that would make a redwood forest jealous. My homepage had 847 DOM elements. Google's recommended maximum is 1,500 for an entire page.

The database was a mess. Years of plugin installs left orphaned tables, revision history eating 80MB, and transients that never expired. I had 12MB of actual content and 340MB of database bloat.




The New Stack



React + Vite + Tailwind CSS + Supabase + Vercel

MetricAfterImprovement
Homepage load0.8s**81% faster**
Time to Interactive1.2s**80% faster**
PageSpeed score97/100**+131%**
JavaScript bundle78KB gzipped**Custom**
Database queries per page2-4**98% fewer**
Monthly cost$0**Free**

The monthly cost drop isn't a flex — it's a side effect of modern tooling. Vercel's hobby tier handles my traffic. Supabase's free tier covers my database. The only thing I pay for is my domain.




Why I Didn't Choose Next.js



This is the question I get most often. Everyone assumes a React blog means Next.js.

I considered it. Here's why I went with Vite instead:

I don't need SSR. This is a content blog, not a dashboard. Every post is static at deploy time. Vite's static site generation is simpler, faster to build, and produces cleaner output.

Build speed matters more than features. Vite cold builds in 3 seconds. Next.js takes 15-30 seconds for the same content. When I'm iterating on a post and want to preview changes, that difference adds up.

Dependency overhead. Next.js pulls in 47 dependencies for a basic app. Vite needs 12. Fewer dependencies means fewer audit warnings, smaller attack surface, and faster npm install.

I control the routing. Next.js file-based routing is convenient until you want something unconventional. Vite lets me define routes explicitly in code. It's more verbose but I always know exactly what's happening.

The tradeoff: I lose automatic image optimization and API routes. I solved images with a build-time sharp script. I don't need API routes — Supabase handles all my backend needs.




The Migration Process (Step by Step)



This wasn't a weekend project. It took two weeks of evenings, mostly because I kept getting distracted by optimization rabbit holes.

Week 1: Content Export and Cleaning



Step 1: Extract content from WordPress

WordPress's export XML is bloated. I wrote a Python script to parse it and strip out Elementor markup, shortcodes, and plugin-generated HTML.

The script took 23 WordPress posts and produced clean Markdown. But it also revealed how much garbage Elementor had injected:

- 847 div wrappers with Elementor class names
- 34 inline style blocks
- 12 instances of data-elementor-type attributes
- 6 embedded Font Awesome icon scripts

I deleted all of it. The posts lost their fancy layouts, but gained readability. Content should be content, not a styling system.

Step 2: Rewrite URLs and fix images

WordPress stored images in wp-content/uploads/2024/03/. I moved everything to /images/posts/ with descriptive filenames. IMG_4732.jpg became postgres-connection-pooling-diagram.webp.

This took six hours but was worth it. Descriptive filenames help SEO, and WebP conversion cut image sizes by 60% on average.

Step 3: Audit and categorize

WordPress had 47 tags. I consolidated to 12 meaningful ones. Tags aren't decoration — they're navigation. If a reader clicks "Redis," they should find every Redis post, not half of them because the other half was tagged "cache."

Week 2: Build and Deploy



Step 4: Design the component system

I built five core components:

1. PostCard — Blog listing cards with lazy-loaded images
2. PostContent — Markdown renderer with syntax highlighting
3. TagCloud — Weighted tag navigation
4. RelatedPosts — Tag-matching recommendations
5. NewsletterForm — Email capture with Supabase backend

Everything else is composition of these five. No third-party UI libraries. No bloated component frameworks.

Step 5: Performance budget enforcement

I set hard limits in my build pipeline:

- Total JS bundle: <100KB gzipped
- First image: load within 500ms
- Largest Contentful Paint: <1.5s
- Cumulative Layout Shift: <0.1

If a build violates any of these, it fails. This prevents the gradual performance death that killed my WordPress site.

Step 6: Database schema design

Supabase posts table:

CREATE TABLE posts (
  id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
  slug text UNIQUE NOT NULL,
  title text NOT NULL,
  excerpt text NOT NULL,
  content text NOT NULL,
  cover_image text,
  category text NOT NULL,
  tags text[] DEFAULT '{}',
  reading_time integer,
  published_at timestamptz,
  is_featured boolean DEFAULT false,
  created_at timestamptz DEFAULT now()
);

CREATE INDEX idx_posts_published ON posts(published_at DESC);
CREATE INDEX idx_posts_category ON posts(category);
CREATE INDEX idx_posts_tags ON posts USING GIN(tags);


Notice the indexes. published_at DESC makes the homepage query instant. GIN on tags makes related-post matching fast. WordPress had none of these optimizations — it was scanning 187 rows every page load.

Step 7: Deploy and monitor

Vercel deploys on every git push. I added three monitoring checks:

1. Lighthouse CI — runs on every PR, blocks if score drops below 95
2. Bundle analyzer — warns if any dependency adds >10KB
3. Uptime monitoring — ping every 5 minutes, alert on 2 failures




The Performance Breakdown



Here's exactly where the 70% improvement came from:

OptimizationImpact
Static generation (no server rendering)-1.2s
WebP images + lazy loading-0.9s
Minimal JavaScript (78KB vs 890KB)-0.6s
Database indexes (2 queries vs 187)-0.4s
No plugin CSS/JS bloat-0.3s
**Total****-3.4s (81%)**

The biggest single win was static generation. WordPress runs PHP, queries MySQL, and assembles the page on every request. My site is pre-built HTML served from a CDN edge node. The server does zero work per request.




What I Miss About WordPress



Let's be fair. Not everything was worse:

The media library. Drag, drop, done. Now I write a Python script to optimize, convert, and upload images. It's faster once set up, but there's no GUI for my girlfriend to use.

Yoast SEO. I built my own metadata generator, but Yoast's real-time feedback was nice. My system works, but it's less forgiving if I forget a meta description.

The ecosystem. Need a contact form? WordPress has 50 plugins. I had to build mine in an afternoon. It's leaner and does exactly what I need, but it cost me time.

One-click staging. Vercel previews every PR automatically, which is arguably better, but WordPress's staging clone was easier to explain to non-developers.




What I Don't Miss At All



Plugin updates. I haven't had a mysterious white-screen-of-death since the migration. My build either passes or fails predictably.

Database bloat. Supabase is at 14MB for the same content that needed 340MB in WordPress. No revisions, no transients, no orphaned tables.

Shared hosting neighbors. Vercel's edge network serves from the closest of 100+ locations. My site loads faster in Tokyo than my old shared hosting loaded in Toronto.

SEO plugin upsells. "Upgrade to Pro for redirects!" I wrote a 15-line redirect handler in my router. Free forever.




Should You Migrate Too?



Yes, if:
- Your site is content-focused (blog, portfolio, documentation)
- You're comfortable writing React components
- Performance scores matter to you (clients, employers, pride)
- You spend more time fixing WordPress than writing content

No, if:
- You need complex user roles and permissions
- Non-technical people update content regularly
- You rely on specific plugins (membership systems, booking engines)
- You value convenience over control

Maybe, if:
- You have 100+ posts. The migration effort scales with content volume. My 23 posts took two weeks. A 500-post site might take two months.
- You use advanced WordPress features. Custom post types, taxonomies, and ACF fields need equivalent structures in the new stack.




The Code That Made It Possible



My vite.config.ts — the entire build configuration:

import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react'
import { globSync } from 'glob'

// Find all Markdown posts and generate routes
const posts = globSync('content/posts/**/*.md')
const routes = posts.map(file => {
  const slug = file.replace('content/posts/', '').replace('.md', '')
  return `/blog/${slug}`
})

export default defineConfig({
  plugins: [react()],
  build: {
    rollupOptions: {
      input: {
        main: 'index.html',
        ...routes.reduce((acc, route) => {
          acc[route] = `src/entries${route}.html`
          return acc
        }, {})
      }
    }
  }
})


That's it. No complex framework config. No hidden magic. Just explicit routes pointing to explicit files.




Results After 3 Months



The migration happened in March. Here's what changed:

MetricBeforeAfterChange
Average session duration1:422:38**+55%**
Bounce rate68%41%**-40%**
Pages per session1.83.2**+78%**
Newsletter signups/mo1247**+292%**
Contact form submissions3/mo11/mo**+267%**

Speed doesn't just feel better — it converts better. Readers who don't wait for pages to load read more pages. People who read more pages subscribe more often. Subscribers become clients.

That $300 project I lost to a broken form? I've landed three since the migration. Two found me through search, one through a newsletter signup. The performance investment paid for itself in the first month.




What's Next



The stack isn't done evolving. Two experiments in progress:

Edge caching with Vercel KV. Currently regenerating related posts on every deploy. Moving to cached recommendations that update incrementally.

AI-assisted content formatting. Building a pipeline that suggests heading structure, reading time estimates, and tag recommendations based on draft content. Not replacing my writing — augmenting it.




The Bottom Line



WordPress is fine for what it is. But "fine" wasn't enough for me. I wanted predictable performance, clean code I fully understood, and zero surprise failures from plugin updates.

The custom stack costs me $0/month, loads in 0.8 seconds, and hasn't broken once in three months of daily deployments.

That's not a knock on WordPress. That's just what happens when you build exactly what you need and nothing more.




Want to see the site? [codehustle.tech](https://codehustle.tech) — built with everything described above.

Questions about the migration? Drop them below. I documented the entire process because I wished someone had documented it for me.
R

Md. Rony Ahmed

Backend Software Engineer · 4+ years production systems · AI microservices & distributed systems

GitHub · LinkedIn · About

Related Posts

How I Cut API Response Time by 73% With a Redis Strategy Nobody Talks About
Programming

How I Cut API Response Time by 73% With a Redis Strategy Nobody Talks About

React 19 New Features: Complete Guide to the Latest Updates and Breaking Changes
Web Development

React 19 New Features: Complete Guide to the Latest Updates and Breaking Changes