Your Old Site Isn't Slow Because It's Old. It's Slow Because Nobody's Touched the Foundation.
Every few months a client lands in my inbox with the same story, just wearing a different framework. This time it isn't a React rebuild that tanked their rankings — it's the opposite direction. They've got a ten-year-old WordPress install groaning under forty plugins, or a PHP site someone's cousin built in 2014, or a pile of static HTML pages that were hand-copied so many times nobody remembers which one has the correct meta description anymore. Traffic has been flat or sliding for years, and everyone assumes the fix is "more content" or "more backlinks." It's usually neither.
Open the page source on these sites and the story is almost always the same: bloated HTML tables from a page builder, three different jQuery versions loading on every page whether they're needed or not, a<title> tag that says "Home" on every single page, and images that are 4MB JPEGs served at 300px wide. Google can crawl this fine — that's not the problem old sites have. The problem is everything else: load time, duplicate or missing metadata, no structured sharing data, and a codebase so tangled that fixing one plugin conflict breaks two others.
Rebuilding these in Next.js isn't about chasing a trendy framework. It's about getting three things right that the old stack made structurally difficult to get right at all.
1. Rendering that doesn't fight the CMS
Old PHP and WordPress sites already render server-side HTML, which is good — but it's usually wrapped in so much unnecessary markup and blocking JavaScript that the actual content takes seconds to become usable. Migrating to Next.js with static generation or incremental static regeneration keeps the good part (HTML on first load) and strips the bad part (plugin bloat, render-blocking scripts, database queries running on every request).
Common issues I find in legacy migrations:
- WordPress pages making 15+ database queries per load because of stacked plugins
- Static HTML sites with copy-pasted
<head>blocks that drift out of sync page to page - PHP includes that pull in unused CSS/JS on every route regardless of what the page needs
- No caching layer, so every visitor triggers a full server render from scratch
generateStaticParams for blog posts, product pages, or service pages means the HTML is built once and served instantly, with ISR handling updates without a full redeploy.
2. Metadata that was never structured in the first place
This is where old sites lose the most ground quietly. WordPress SEO plugins often can do this right, but half the installs I audit have them misconfigured, disabled, or fighting with a second SEO plugin someone installed and forgot about. Static HTML sites are worse — metadata is usually copy-pasted once and never touched again.
The fix isn't complicated, it's just centralized. One component, driven by real data, used on every page:
// components/Seo.jsx
// Works as a drop-in wrapper for any page — Next.js App Router or Pages Router.
// For App Router, prefer exporting generateMetadata() per page and reuse buildMeta() below.
export function buildMeta({
title,
description,
keywords = [],
url,
image = '/og-default.jpg',
siteName = 'Your Site Name',
twitterHandle = '@yourhandle',
type = 'website',
}) {
const fullTitle = title ? `${title} | ${siteName}` : siteName;
return {
title: fullTitle,
description,
keywords: keywords.join(', '),
alternates: {
canonical: url,
},
openGraph: {
title: fullTitle,
description,
url,
siteName,
images: [{ url: image, width: 1200, height: 630, alt: title }],
type, // 'website' | 'article' | 'product'
// LinkedIn reads Open Graph tags directly, no separate tags needed
},
twitter: {
card: 'summary_large_image',
title: fullTitle,
description,
images: [image],
site: twitterHandle,
},
};
}
// Usage in an App Router page (app/blog/[slug]/page.js):
// export async function generateMetadata({ params }) {
// const post = await getPost(params.slug);
// return buildMeta({
// title: post.title,
// description: post.excerpt,
// keywords: post.tags,
// url: `https://yoursite.com/blog/${post.slug}`,
// image: post.coverImage,
// type: 'article',
// });
// }
For teams migrating a Pages Router or a non-Next.js React frontend still mid-transition, the same object works with next/head:
// components/SeoHead.jsx (Pages Router / legacy React fallback)
import Head from 'next/head';
import { buildMeta } from './Seo';
export default function SeoHead(props) {
const meta = buildMeta(props);
return (
<Head>
<title>{meta.title}</title>
<meta name="description" content={meta.description} />
<meta name="keywords" content={meta.keywords} />
<link rel="canonical" href={meta.alternates.canonical} />
<meta property="og:title" content={meta.openGraph.title} />
<meta property="og:description" content={meta.openGraph.description} />
<meta property="og:url" content={meta.openGraph.url} />
<meta property="og:image" content={meta.openGraph.images[0].url} />
<meta property="og:type" content={meta.openGraph.type} />
<meta property="og:site_name" content={meta.openGraph.siteName} />
<meta name="twitter:card" content={meta.twitter.card} />
<meta name="twitter:title" content={meta.twitter.title} />
<meta name="twitter:description" content={meta.twitter.description} />
<meta name="twitter:image" content={meta.twitter.images[0]} />
<meta name="twitter:site" content={meta.twitter.site} />
</Head>
);
}
One function, called per page, fed by real content instead of a plugin's best guess. Facebook and LinkedIn both read the og: tags, so there's no separate LinkedIn-specific markup needed — get Open Graph right once and both platforms unfurl correctly.
3. Speed that a plugin can't patch
Old sites accumulate speed problems the way a house accumulates clutter — nobody adds it all at once, it just piles up over years of "quick fixes." A caching plugin here, an image optimization plugin there, none of them talking to each other. Migrating gives you a chance to fix this structurally instead of patching it:
- Replace raw
<img>tags withnext/image, which handles resizing, lazy loading, and modern formats automatically - Cut unused plugin CSS/JS — most WordPress themes ship 10x more than any given page uses
- Move third-party scripts (analytics, chat widgets, tracking pixels) to load after the page is interactive, not before
- Audit fonts — legacy sites frequently load 4–6 font weights from multiple providers when two would do
None of this requires new content or a link-building budget. It's fixing a foundation that was never built for how search engines — or AI answer engines, which crawl even less patiently — actually read a page today.
I've taken sites from a 25 PageSpeed score to the 90s and watched organic traffic climb back within two months, without touching a word of the existing content. The old site wasn't wrong. It just needed a structure underneath it that Google and every crawler after it can actually read quickly and clearly.
Running an old PHP, WordPress, or static site and not sure what's holding it back? Send me your URL and I'll show you exactly what's slowing it down and what a Next.js remodel would fix.

