I've lost count of the number of times a client has come to us with some variation of the same story: "Our WordPress site got hacked. We cleaned it up. It got hacked again. We're done." The last one was a mid-market ecommerce company whose WooCommerce store had been injected with a credit card skimmer buried inside a legitimate-looking plugin update. They'd been leaking customer payment data for three weeks before anyone noticed. That's not an edge case. That's Tuesday in the WordPress ecosystem.

This article isn't about bashing WordPress. It powered a huge chunk of the web for good reason. But the architecture that made it accessible in 2005 is the same architecture that makes it a magnet for automated attacks in 2025. If you've been hacked -- or you're tired of spending money on security plugins that are themselves attack vectors -- this is your playbook for moving to something fundamentally more secure.

Table of Contents

WordPress Hacked? Why Migration to Next.js + Supabase Is Your Best Fix

The WordPress Security Problem Is Architectural

Let me be clear about something: the WordPress core team does solid security work. WordPress core, kept up to date, is reasonably secure. But nobody runs just WordPress core. The average WordPress site has 20-30 plugins installed. Each one is a dependency you didn't write, maintained by someone you don't know, with access to your database, your filesystem, and your users' data.

Here's the thing that keeps getting missed in "WordPress security best practices" articles: the problem isn't that WordPress site owners are negligent. The problem is that WordPress's architecture requires you to install executable PHP code from third parties directly on your server to get basic functionality. That's the equivalent of giving every contractor who works on your house a copy of your house key -- permanently.

WPScan's vulnerability database tracked over 7,900 new WordPress vulnerabilities in 2024, with plugins accounting for roughly 96% of them. Sucuri's 2024 threat report found that WordPress accounted for approximately 95% of all CMS infections they cleaned up. And Patchstack reported that 33% of critical WordPress vulnerabilities in 2024 had no patch available at the time of disclosure.

These aren't bugs that can be fixed with better coding practices. They're emergent properties of the architecture itself.

Common WordPress Attack Vectors in 2025

Before we talk about the fix, let's catalog what you're actually defending against. I've personally triaged dozens of compromised WordPress sites, and the attacks fall into predictable patterns.

SQL Injection Through Plugins

WordPress uses a MySQL database with a well-documented schema. Every plugin that accepts user input and touches the database is a potential SQL injection point. The $wpdb->prepare() function exists, but it's opt-in. Plugin developers forget it, misuse it, or skip it entirely for "simple" queries.

I once traced an injection back to a contact form plugin that had been abandoned for 18 months but was still installed on 200,000+ sites. The attacker was using a UNION-based injection to dump the wp_users table, grab admin password hashes, and crack the weak ones offline.

-- What a typical WordPress SQL injection looks like in logs
GET /wp-content/plugins/vulnerable-plugin/ajax.php?id=1%20UNION%20SELECT%201,user_login,user_pass,4,5%20FROM%20wp_users--

PHP Object Injection and Remote Code Execution

WordPress's heavy use of serialize() and unserialize() creates opportunities for PHP object injection. When a plugin unserializes user-controlled data (and many do), an attacker can craft payloads that execute arbitrary code during the unserialization process.

In 2024, a critical RCE vulnerability in a popular backup plugin (installed on 5 million+ sites) allowed unauthenticated attackers to execute arbitrary PHP code. The fix took 11 days to ship. Eleven days where every site with that plugin was a sitting duck.

Plugin Supply Chain Attacks

This is the one that scares me most. Attackers buy abandoned plugins with large install bases, push a "security update" containing a backdoor, and WordPress auto-update mechanisms distribute the malware to every site running that plugin. It happened with Display Widgets (300,000 installs) and Social Warfare (70,000 installs), and those are just the ones that got caught.

Brute Force Attacks on wp-login.php

Every WordPress site exposes /wp-login.php and /xmlrpc.php by default. Automated botnets hit these endpoints constantly. Wordfence reported blocking an average of 3 billion malicious requests per month across their network in 2024. Even with rate limiting and two-factor auth, you're spending server resources processing these attacks.

Cross-Site Scripting (XSS) via Themes and Plugins

Stored XSS in WordPress is particularly dangerous because the admin dashboard and the public-facing site share the same session context. An XSS payload injected through a comment, a form submission, or a vulnerable plugin setting can escalate to full admin access.

Why Headless Architecture Eliminates Entire Attack Categories

Here's where things get interesting. A headless architecture doesn't just reduce your attack surface -- it eliminates entire categories of attacks by removing the conditions that make them possible.

In a traditional WordPress setup, the same server that renders your HTML also:

  • Runs PHP code from 20+ third-party plugins
  • Manages user authentication
  • Connects to the database
  • Serves the admin interface
  • Handles file uploads
  • Processes form submissions

That's a lot of responsibilities for a single application. In a headless setup with Next.js and Supabase, these responsibilities are separated across isolated services:

  • Frontend (Next.js on Vercel/Netlify): Static HTML/JS served from a CDN. No server-side runtime exposed to the public internet in most cases.
  • Database + Auth (Supabase): Managed Postgres with Row Level Security, never directly exposed to end users.
  • API Layer: Serverless functions with explicit, minimal endpoints.
  • CMS (if needed): Headless CMS running on its own isolated infrastructure.

There's no PHP to inject into. There's no plugin directory with write access. There's no shared session between the admin and the public site. There's no wp-login.php for bots to pound on.

You don't need a WAF to protect an attack surface that doesn't exist.

WordPress Hacked? Why Migration to Next.js + Supabase Is Your Best Fix - architecture

Next.js + Supabase: A Security-First Stack

Let's get specific about why this particular combination works so well from a security standpoint.

Next.js: The Frontend That Doesn't Run Code

When you build a Next.js site with static generation (SSG) or incremental static regeneration (ISR), what gets deployed is HTML, CSS, and JavaScript files on a CDN. There's no application server processing requests in real-time. You can't SQL-inject a CDN.

For dynamic functionality, Next.js Server Actions and Route Handlers run as serverless functions. Each function is:

  • Isolated in its own execution context
  • Stateless (no shared memory between requests)
  • Short-lived (cold start, execute, terminate)
  • Explicitly defined (no auto-discovery of endpoints)
// Next.js Route Handler -- explicit, typed, minimal
import { createClient } from '@/lib/supabase/server'
import { NextRequest, NextResponse } from 'next/server'
import { z } from 'zod'

const ContactSchema = z.object({
  name: z.string().min(1).max(100),
  email: z.string().email(),
  message: z.string().min(10).max(5000),
})

export async function POST(request: NextRequest) {
  const body = await request.json()
  const parsed = ContactSchema.safeParse(body)
  
  if (!parsed.success) {
    return NextResponse.json({ error: 'Invalid input' }, { status: 400 })
  }
  
  const supabase = await createClient()
  const { error } = await supabase
    .from('contact_submissions')
    .insert(parsed.data)
  
  if (error) {
    return NextResponse.json({ error: 'Submission failed' }, { status: 500 })
  }
  
  return NextResponse.json({ success: true })
}

Compare that to a WordPress contact form plugin that has to hook into WordPress's action system, include its own AJAX handler, manage its own nonce verification, and build its own SQL queries. The Next.js version has fewer moving parts, validated input via Zod, and parameterized queries through the Supabase client.

Supabase: Postgres with Row Level Security

Supabase gives you a managed PostgreSQL database with one killer security feature: Row Level Security (RLS). Instead of trusting your application code to enforce access control (the WordPress model), you define security policies at the database level.

-- Only authenticated users can read their own data
CREATE POLICY "Users can view own profile"
  ON profiles
  FOR SELECT
  USING (auth.uid() = user_id);

-- Public can insert into contact_submissions but not read
CREATE POLICY "Anyone can submit contact form"
  ON contact_submissions
  FOR INSERT
  WITH CHECK (true);

CREATE POLICY "Only admins can read submissions"
  ON contact_submissions
  FOR SELECT
  USING (auth.jwt() ->> 'role' = 'admin');

Even if an attacker finds a way to make arbitrary Supabase queries (which is already much harder without a PHP execution context), RLS policies prevent them from accessing data they shouldn't see. This is defense in depth that WordPress fundamentally can't offer because its permission system is implemented in PHP code, not at the database layer.

Supabase also handles authentication with built-in support for email/password, magic links, OAuth providers, and multi-factor auth. No plugin required. No third-party code running on your server.

Attack Surface Comparison: WordPress vs Headless

Let's put this side by side.

Attack Vector WordPress Next.js + Supabase
SQL Injection High risk -- plugins build raw queries Near zero -- parameterized queries via Supabase client, RLS as backup
PHP/Remote Code Execution High risk -- plugins execute server-side PHP Not applicable -- no PHP runtime
Plugin Supply Chain Critical risk -- auto-updates distribute malware Not applicable -- no plugin ecosystem
Brute Force (login) Always exposed (wp-login.php, xmlrpc.php) Admin access through Supabase Auth or separate dashboard, no public login endpoint required
XSS (Stored) High risk -- shared admin/public context Low risk -- React escapes output by default, admin and public are separate apps
File Upload Exploitation High risk -- uploaded PHP files can execute Low risk -- uploads go to object storage (Supabase Storage/S3), never executed as code
Database Exposure Direct MySQL access if server is compromised Database behind Supabase's infrastructure, RLS policies as final guard
DDoS on Origin Server must process every request Static assets on CDN, origin rarely hit
Known Path Enumeration wp-admin, wp-content, wp-includes all scannable No predictable paths, no exposed admin routes

The Migration Playbook: WordPress to Next.js + Supabase

Alright, you're convinced (or your hacked site convinced you). Here's how to actually do this. We've run this migration at Social Animal enough times to have a repeatable process.

Phase 1: Triage and Content Audit (Week 1)

Before you touch any code, you need to understand what you actually have.

  1. Export all WordPress content using WP-CLI or the REST API. Don't rely on XML exports -- they miss meta fields and custom post types.
  2. Catalog all functionality provided by plugins. Make a spreadsheet: plugin name, what it does, whether you actually need it, and what replaces it.
  3. Map URL structures for SEO preservation. Every existing URL needs a redirect or a matching route in Next.js.
  4. Identify dynamic features -- forms, search, user accounts, ecommerce -- that need API endpoints.
# Export WordPress content via REST API
curl -s "https://yoursite.com/wp-json/wp/v2/posts?per_page=100&page=1" | jq '.' > posts_page1.json
curl -s "https://yoursite.com/wp-json/wp/v2/pages?per_page=100" | jq '.' > pages.json
curl -s "https://yoursite.com/wp-json/wp/v2/media?per_page=100" | jq '.' > media.json

Phase 2: Supabase Schema and Data Migration (Week 2)

Design your Supabase database schema based on the content audit. Don't just replicate the WordPress schema -- it's bloated with metadata tables and serialized data blobs.

-- Clean, purpose-built schema
CREATE TABLE posts (
  id UUID DEFAULT gen_random_uuid() PRIMARY KEY,
  title TEXT NOT NULL,
  slug TEXT UNIQUE NOT NULL,
  content TEXT,
  excerpt TEXT,
  featured_image TEXT,
  status TEXT DEFAULT 'draft' CHECK (status IN ('draft', 'published', 'archived')),
  published_at TIMESTAMPTZ,
  created_at TIMESTAMPTZ DEFAULT NOW(),
  updated_at TIMESTAMPTZ DEFAULT NOW(),
  author_id UUID REFERENCES auth.users(id)
);

-- Enable RLS immediately
ALTER TABLE posts ENABLE ROW LEVEL SECURITY;

CREATE POLICY "Published posts are public"
  ON posts FOR SELECT
  USING (status = 'published');

CREATE POLICY "Authors can manage own posts"
  ON posts FOR ALL
  USING (auth.uid() = author_id);

Write a migration script (Node.js or Python) that transforms WordPress JSON exports into your new schema and inserts them into Supabase.

Phase 3: Next.js Build (Weeks 3-5)

Build your Next.js frontend. If you're working with a team that knows the stack, this goes fast. If you need help, our Next.js development team has done this migration enough times to have strong opinions about the right patterns.

Key architectural decisions:

  • Static generation for content pages -- blog posts, landing pages, about pages. These become HTML files on a CDN.
  • Server components for dynamic data -- fetch from Supabase at request time with caching.
  • Route handlers for form submissions -- contact forms, newsletter signups, etc.
  • Middleware for redirects -- handle all your old WordPress URLs.
// next.config.ts -- handle WordPress URL redirects
const nextConfig = {
  async redirects() {
    return [
      {
        source: '/category/:slug',
        destination: '/blog/category/:slug',
        permanent: true,
      },
      {
        source: '/:year(\\d{4})/:month(\\d{2})/:slug',
        destination: '/blog/:slug',
        permanent: true,
      },
      // wp-login.php -- send bots to a 410
      {
        source: '/wp-login.php',
        destination: '/gone',
        permanent: true,
      },
      {
        source: '/wp-admin/:path*',
        destination: '/gone',
        permanent: true,
      },
    ]
  },
}

Phase 4: Testing and SEO Validation (Week 6)

  • Run Screaming Frog against the new site to verify every old URL either resolves or redirects.
  • Validate structured data (JSON-LD) is present on all pages.
  • Test all forms and dynamic features.
  • Run Lighthouse and Core Web Vitals checks -- you'll almost certainly see improvements since you're now serving from a CDN.
  • Set up monitoring with Vercel Analytics or your preferred tool.

Phase 5: Launch and DNS Cutover (Week 6-7)

Deploy to Vercel or Netlify, update DNS, and set up monitoring. Keep the old WordPress instance offline but accessible for 30 days in case you need to reference anything.

If your site has significant traffic or ecommerce functionality, consider a headless CMS integration for content management and talk to us about Astro as an alternative frontend for content-heavy sites where build performance matters.

Post-Migration Security Hardening

Once you're on the new stack, here's your security checklist:

  1. Enable Supabase RLS on every table. No exceptions. If a table doesn't have policies, it's either inaccessible (good default) or wide open (bad).
  2. Use environment variables for all secrets. Vercel and Netlify both handle this well. Never commit API keys.
  3. Set up Supabase database backups. Point-in-time recovery is available on Pro plans ($25/month).
  4. Configure Content Security Policy headers in your Next.js middleware.
  5. Enable Vercel's DDoS protection (included on all plans).
  6. Set up uptime monitoring -- we use Better Uptime, but Checkly and Vercel's built-in monitoring work too.
  7. Audit your Supabase RLS policies quarterly. Use Supabase's SQL editor to test policies with different user contexts.
// middleware.ts -- security headers
import { NextResponse } from 'next/server'
import type { NextRequest } from 'next/server'

export function middleware(request: NextRequest) {
  const response = NextResponse.next()
  
  response.headers.set('X-Frame-Options', 'DENY')
  response.headers.set('X-Content-Type-Options', 'nosniff')
  response.headers.set('Referrer-Policy', 'strict-origin-when-cross-origin')
  response.headers.set(
    'Content-Security-Policy',
    "default-src 'self'; script-src 'self' 'unsafe-inline' 'unsafe-eval'; style-src 'self' 'unsafe-inline';"
  )
  response.headers.set(
    'Permissions-Policy',
    'camera=(), microphone=(), geolocation=()'
  )
  
  return response
}

Real Cost of WordPress Security vs Migration

Let's talk money, because that's what actually drives decisions.

Cost Category WordPress (Annual) Next.js + Supabase (Annual)
Hosting $300-1,200 (managed WP) $0-240 (Vercel Pro)
Security plugins (Wordfence/Sucuri) $200-500 $0 (not needed)
SSL/CDN $0-200 $0 (included)
Malware cleanup (if hacked) $500-2,500 per incident N/A
Developer time on security patches $2,000-5,000 $500-1,000
Database (Supabase) Included in hosting $0-300 (Free tier to Pro)
Total $3,000-9,400 $500-1,540

And that's before you factor in the cost of downtime, lost customer trust, and potential regulatory penalties if customer data is compromised. A single GDPR-reportable breach can cost tens of thousands in legal and compliance overhead.

The migration itself typically costs between $10,000-40,000 depending on site complexity. For most businesses, that pays for itself within 1-2 years in reduced security spending alone -- and you get a faster, more maintainable site in the process. Check our pricing page for specifics on migration projects.

FAQ

Is WordPress really that insecure, or is this overblown? WordPress core is maintained well. The problem is that virtually no one runs just core. The plugin ecosystem is where 96% of vulnerabilities live, and you can't run a useful WordPress site without plugins. It's not overblown -- Sucuri cleaned malware from over 60,000 WordPress sites in 2024 alone. The architecture requires you to trust third-party PHP code on your server, and that trust is exploited constantly.

Can't I just use better security plugins instead of migrating? Security plugins are themselves PHP code running on your server with deep system access. Wordfence and Sucuri are well-maintained, but they're band-aids on an architectural problem. They also add server load, can conflict with other plugins, and have had their own vulnerabilities over the years. You're adding complexity to solve a complexity problem.

How long does a WordPress to Next.js migration typically take? For a standard business site (10-50 pages, blog, contact forms), we typically complete migrations in 5-7 weeks. Ecommerce sites with WooCommerce are more complex and can take 8-14 weeks depending on product catalog size and custom functionality. If you have a simpler site, reach out to us and we can give you a more specific timeline.

Will I lose my SEO rankings when migrating from WordPress? Not if you do it right. The critical steps are: preserve all URL structures or set up proper 301 redirects, maintain your structured data markup, keep your content intact, and submit updated sitemaps to Google Search Console. Most of our migration clients see ranking improvements within 2-3 months because Core Web Vitals scores improve dramatically when you move to a CDN-served static site.

What about content editing? WordPress is easy for non-technical users. This is a legitimate concern. You have options: Supabase with a custom admin dashboard, or a headless CMS like Sanity, Contentful, or Payload CMS that gives content editors a visual editing experience similar to WordPress. We handle headless CMS integrations regularly and can recommend the right fit for your team's needs.

Is Supabase secure enough for production use? Supabase runs on AWS infrastructure with SOC 2 Type II compliance. The underlying database is PostgreSQL, which has a strong security track record. Row Level Security policies enforce access control at the database level, which is actually more secure than WordPress's PHP-based permission system. Supabase also offers point-in-time recovery, encrypted connections, and network restrictions on paid plans.

What if my WordPress site has been hacked and I need immediate help? First, take the site offline immediately to stop further damage and data leakage. Second, preserve forensic evidence (database dumps, access logs, file system snapshots). Third, don't just clean it and put it back -- you'll likely be reinfected within weeks. Use the incident as the catalyst to migrate. Contact our team and we can help with both immediate triage and migration planning.

Do I need to migrate everything at once, or can I do it incrementally? Incremental migration is possible but adds complexity. You can run Next.js as the frontend while keeping WordPress as a headless CMS backend temporarily, then phase out WordPress entirely. However, this means WordPress is still running and still needs security maintenance during the transition. For most sites, a clean cutover is faster, cheaper, and eliminates security risk sooner.