WordPress to Next.js Migration: How Botric Did It in Days

    maheshMarch 24, 202618 min read
    WordPress to Next.js Migration: How Botric Did It in Days

    When we launched Botric, our marketing site was built on WordPress. It was the obvious choice, quick setup, thousands of plugins, and a familiar admin panel. But as our AI-powered growth platform matured, the gap between what our product represented and what our website delivered became impossible to ignore.

    Performance started becoming a real problem. Page load times were inconsistent, Core Web Vitals kept dropping, and even small updates felt risky. For a company that helps businesses grow with AI, a sluggish WordPress site sent the wrong message.

    So we rebuilt everything.

    We migrated Botric, with over 14 pages and 16 blog posts, from WordPress to a statically exported Next.js 14 application deployed on Vercel. The result is a faster, more secure, and developer-friendly site that is optimized for discovery by AI search engines like ChatGPT, Claude, and Perplexity.

    This post walks through the entire migration, including why we did it, what we chose, how we executed it, and what we learned along the way.

    WordPress vs Next.js: Side by Side

    Before diving into how we migrated, here is an honest comparison of what changed across the areas that mattered most to us:

    CategoryWordPressNext.js + Vercel
    CostPHP/MySQL hosting + managed DB + premium plugins add up quicklyRuns on the company's Vercel account with no plugin licensing costs
    Developer ExperienceSeparate PHP/theme system; hard to align with product codebaseSame TypeScript + React stack as the product; full component reuse
    Content WorkflowWordPress admin panel; plugin-dependent; revision history in databaseMDX files in Git; branch-based drafts; full diff history; Claude Code for non-devs
    SEOYoast and similar plugins; manual meta management; limited controlNative Metadata API; dynamic OG tags; full control over robots.txt and sitemap
    SecurityConstant plugin patching; PHP/MySQL attack surface; most-attacked CMS on the internetNo server runtime, no database, no plugins. Drastically smaller attack surface
    AI Crawler Support (GEO)Usually blocked or ignored by defaultExplicitly allows GPTBot, Claude-Web, PerplexityBot in robots.txt
    ScalabilityRequires DB tuning, PHP workers, load balancers at scaleStatic CDN scales automatically. No config changes needed
    Vendor Lock-inTied to WordPress ecosystem, themes, pluginsJust files in a Git repo, portable, auditable, yours

    The Tech Stack: What We Chose and Why

    Every technology decision was driven by a single principle: ship static HTML from a global CDN, with zero server compute in production. This is the same approach used by many high-performance Next.js marketing websites.

    Here is a breakdown of what we chose and why:

    TechnologyWhy We Chose It
    Next.js 14 (App Router)File-based routing, React Server Components, and native static export via output: 'export'
    React 18 + TypeScriptComponent architecture with type safety across the entire codebase
    Tailwind CSS + Shadcn/UIUtility-first CSS paired with accessible, Radix-based UI primitives
    Framer MotionSmooth page transitions and micro-interactions without heavyweight animation libraries
    MDX (next-mdx-remote + gray-matter)Blog posts as version-controlled markdown files with embedded JSX components
    VercelZero-config deployment, global edge CDN, built-in security headers, instant rollbacks

    The critical line in our next.config.mjs is just three properties:

    const nextConfig = {
      output: "export",
      images: { unoptimized: true },
      pageExtensions: ["js", "jsx", "ts", "tsx", "md", "mdx"],
    };
    

    With output: "export", Next.js pre-renders every page as static HTML at build time. There is no Node.js server running in production. Just HTML, CSS, and JavaScript served from Vercel's edge network to the nearest user.

    This single architectural decision removed entire categories of problems we used to face on WordPress, including server downtime, database connection limits, PHP memory issues, and the constant security patching that comes with a dynamic CMS.

    The Migration Process

    Step 1. The Migration Script

    To handle the bulk of the migration, we wrote a custom Node.js script (scripts/wp-to-markdown.js, 314 lines) that automated most of the work.

    Here's how the workflow looked:

    1. Export from WordPress → We started by downloading the standard WordPress XML export from the admin panel (Tools → Export → All Content).
    2. Parse the XML → Using fast-xml-parser, the script processes each published post and extracts key data like title, slug, publish date, author, categories, tags, excerpt, and the full HTML content.
    3. Convert HTML to Markdown → A custom htmlToMarkdown() function handles headings, bold/italic, links, images, lists, blockquotes, code blocks, figures, and HTML entity decoding. WordPress shortcodes are stripped automatically.
    4. Download and localize images → Every image URL found in the content is downloaded to /public/blog-images/ and the markdown is rewritten to use local paths. No more external image dependencies.
    5. Generate MDX files with frontmatter → Each post is converted into an .mdx file with structured YAML frontmatter, including all relevant metadata.
    6. Create redirect mappings → Old WordPress URLs are mapped to new /blog/[slug] paths and saved to redirects.json for preserving link equity.

    Here is a simplified look at how the HTML-to-Markdown conversion handles key elements:

    // Headings
    md = md.replace(/<h2[^>]*>(.*?)<\/h2>/gi, "## $1\n\n");
    
    // Bold
    md = md.replace(/<strong[^>]*>(.*?)<\/strong>/gi, "**$1**");
    
    // Links
    md = md.replace(/<a[^>]*href="([^"]*)"[^>]*>(.*?)<\/a>/gi, "[$2]($1)");
    
    // Images
    md = md.replace(/<img[^>]*src="([^"]*)"[^>]*alt="([^"]*)"[^>]*\/?>/gi, "![$2]($1)");
    
    // Blockquotes
    md = md.replace(/<blockquote[^>]*>(.*?)<\/blockquote>/gis, (_, content) => {
      return content.split("\n").map((line) => `> ${line.trim()}`).join("\n");
    });
    

    Running the script against our WordPress export took about 30 seconds and produced 16 clean .mdx files with all images localized.

    Step 2. Content Directory Structure

    After migration, our blog content lives in a flat directory structure:

    content/posts/
    ├── 5-signs-ai-support-agent.mdx
    ├── ai-agent-for-ecommerce.mdx
    ├── ai-agent-for-small-business.mdx
    ├── ai-agent-launch-checklist.mdx
    ├── ai-agents-for-customer-support.mdx
    ├── ai-agents-for-healthcare.mdx
    ├── ai-agents-for-saas.mdx
    ├── ai-vs-human-agents-customer-support.mdx
    ├── build-ai-agent-botric-no-code.mdx
    ├── chatbots-vs-ai-agents.mdx
    ├── customer-support-kpis-to-track-ai-agents.mdx
    ├── enterprise-ai-agents-for-b2b.mdx
    ├── generative-engine-optimization-explained.mdx
    ├── roi-of-ai-agents-guide.mdx
    ├── top-ai-customer-service-agents-2026.mdx
    └── why-rag-is-the-missing-piece-in-support-ai.mdx
    

    Each file is self-contained: frontmatter metadata at the top, markdown content below.

    How We Approached SEO and GEO in This Migration

    How We Approached SEO and GEO in This Migration

    1. Page-Level Metadata

    Every page uses the Next.js Metadata API to define title, description, keywords, canonical URLs, robots directives, OpenGraph tags, and Twitter Cards. For blog posts, metadata is generated dynamically.

    export async function generateMetadata({ params }) {
      const { slug } = await params;
      const post = getPostBySlug(slug);
      if (!post) return {};
    
      return {
        title: `${post.title} | Botric Blog`,
        description: post.excerpt,
        openGraph: {
          title: post.title,
          description: post.excerpt,
          type: "article",
          publishedTime: post.date,
          ...(post.featuredImage && { images: [post.featuredImage] }),
        },
      };
    }
    

    This ensures every blog post includes rich metadata for search engines and social sharing, without needing to manually update meta tags in an admin panel.

    2. AI-Optimized robots.txt (GEO Strategy)

    This is our unique differentiator. Most WordPress sites either block AI crawlers or simply ignore them. We took the opposite approach. Our robots.txt explicitly allows every major AI crawler:

    User-agent: *
    Allow: /
    
    Sitemap: https://www.botric.ai/sitemap.xml
    
    User-agent: GPTBot
    Allow: /
    
    User-agent: ChatGPT-User
    Allow: /
    
    User-agent: Claude-Web
    Allow: /
    
    User-agent: PerplexityBot
    Allow: /
    

    Why does this matter? As AI search engines like ChatGPT, Claude, and Perplexity become primary discovery channels, your content needs to be crawlable by their bots. This is known as Generative Engine Optimization (GEO), and it is a core part of what Botric does for clients.

    By explicitly allowing GPTBot, ChatGPT-User, Claude-Web, and PerplexityBot, we ensure our content is indexed and cited by AI assistants when users ask questions about GEO, AI agents, or SaaS growth. Most WordPress-to-Next.js migration guides do not cover this. It is a forward-looking SEO decision that most teams still overlook.

    3. Sitemap

    We maintain a static sitemap.xml with priority levels that reflect page importance:

    This gives search engines and AI crawlers a clear signal of which pages matter most.

    4. URL Redirect Strategy

    The migration script generates a redirects.json file that maps old WordPress URLs to new /blog/[slug] paths using 301 redirects. This preserves link equity and prevents 404 errors.

    5. Structured Content for AI Discovery

    Beyond technical SEO, how we structure content also matters for GEO. Our blog posts use clear heading hierarchies, FAQ-style sections, direct answers to common questions, and structured comparisons.

    AI models tend to favor content that is well organized and directly answers queries, which is exactly what clean Markdown with proper headings supports.

    Performance: Before vs After

    Performance Before vs After

    The biggest improvement did not come from small optimizations. It came from removing entire layers of runtime overhead.

    1. Static Export = Zero Server Compute

    On WordPress, every page request triggered a PHP execution cycle: parse the request, query MySQL, assemble the HTML, send the response. Even with caching plugins, the baseline overhead was significant.

    With Next.js static export, every page is pre-built as HTML during npm run build. In production, Vercel's CDN serves these files from the edge location nearest to each user. The result is consistently fast page loads across locations.

    2. Font Optimization

    On WordPress, fonts were typically loaded from external sources, which often delayed text rendering and sometimes caused flashes of invisible text.

    We use the next/font module to self-host the Sora font family:

    const sora = Sora({
      subsets: ["latin"],
      variable: "--font-sora",
      display: "swap",
    });
    

    The display: "swap" directive ensures text is immediately visible with a fallback font while Sora loads, eliminating the flash of invisible text that plagued our WordPress setup.

    We also add preconnect hints for Google Fonts, Google Analytics, and Calendly to reduce DNS lookup times.

    3. Lazy Loading

    On WordPress, scripts and third-party resources are often loaded upfront, increasing initial load time and blocking rendering.

    With Next.js, performance-critical resources are loaded only when needed:

    4. Image Optimization with WebP

    On WordPress, image optimization depended on plugins or external services, often leading to inconsistent results and larger payload sizes.

    With Next.js, we store all featured images in WebP format, typically reducing file sizes compared to JPEG at similar quality.

    Since static export requires images: { unoptimized: true } (Next.js Image Optimization needs a server), we pre-optimize all images before committing them to the repository.

    5. Security Headers

    On WordPress, security configurations depended on plugins and server setup, which could vary across environments.

    With Next.js, security headers are defined once in vercel.json and applied consistently across all routes.

    {
      "headers": [
        {
          "source": "/(.*)",
          "headers": [
            { "key": "Strict-Transport-Security", "value": "max-age=31536000; includeSubDomains; preload" },
            { "key": "X-Frame-Options", "value": "DENY" },
            { "key": "X-Content-Type-Options", "value": "nosniff" },
            { "key": "Referrer-Policy", "value": "strict-origin-when-cross-origin" },
            { "key": "Content-Security-Policy", "value": "default-src 'self'; script-src 'self' ..." },
            { "key": "Permissions-Policy", "value": "geolocation=(), microphone=(), camera=()" }
          ]
        }
      ]
    }
    

    These headers enforce HTTPS (HSTS with preload), prevent clickjacking (X-Frame-Options: DENY), block MIME-type sniffing, restrict what the page can access (Content-Security-Policy), and disable unnecessary browser APIs (Permissions-Policy). Beyond security, these headers positively influence Lighthouse scores and are a trust signal for search engines.

    6. Skeleton Loading States

    On WordPress, pages often showed a blank screen before content appeared, especially under slower conditions.

    With Next.js, every major page has a custom loading.jsx file that renders skeleton UI with Tailwind's animate-pulse class.

    This gives users an instant visual response while the page content hydrates, dramatically improving perceived performance compared to WordPress's blank-screen-then-sudden-render pattern.

    Business Benefits of Migrating from WordPress to Next.js

    Business Benefits of Migrating from WordPress to Next.js

    The result is not just better performance, but a system that is easier to maintain, scale, and build on over time:

    1. Cost Reduction

    We no longer pay for PHP/MySQL hosting, premium plugins, or managed database services. Our site runs on our company's Vercel account, which handles current traffic comfortably at a lower cost than a typical managed WordPress setup.

    2. Security

    Removing WordPress from our stack reduced a large portion of our attack surface. There is no PHP runtime, no database exposed to queries, and no third-party plugins introducing vulnerabilities. Instead of constantly patching and monitoring, the system is simpler and inherently more secure.

    3. Speed to Deploy

    Deployments are straightforward. Push to Git, and the site is live within seconds. There is no need for FTP uploads, server access, or manual cache clearing.

    4. Scalability

    The site runs as static files served through a global CDN, which means it scales automatically with traffic. There is no need to manage database connections, tune backend processes, or configure load balancers.

    5. Developer Experience

    The entire site is built with the same tools we use for the product, including TypeScript and reusable components. This makes it easier for developers to contribute without switching contexts or learning a separate system.

    6. Brand Consistency

    We use the same design system across both product and marketing, including the Sora font family, an HSL-based color system, and Tailwind design tokens. This keeps the experience consistent without the overhead of managing themes or dealing with plugin conflicts.

    How Claude Code Made This Migration Possible in Days

    Most migration guides focus on tools and architecture, but skip a more practical question: who actually does the work, and how long does it take?

    A project like this, rebuilding 14+ pages, migrating 16 blog posts, setting up an MDX pipeline, configuring SEO metadata, adding security headers, and deploying to Vercel, would normally take a small team several weeks.

    We completed it in a few days. A big part of that came down to using Claude Code, Anthropic's terminal-based AI coding assistant.

    AI-Assisted Development at Every Stage

    Claude Code was not just used for autocomplete. We used it across multiple parts of the workflow:

    Why We Don't Need a CMS Anymore

    This is the bigger story. When we left WordPress, the obvious question was: how will the content team publish without a CMS?

    The typical approach is to adopt a headless CMS like Sanity, Contentful, or Strapi. That usually means another tool to manage, another interface to learn, and another recurring cost.

    We chose a simpler path. Content now lives directly in the codebase as MDX files, managed through Git. Publishing is straightforward. A new post is created as a file, added to the repository, and deployed automatically.

    Formatting is handled through shared MDX components. Images, links, blockquotes, code blocks, and headings follow consistent rules defined once and applied across all posts. There is no plugin layer or per-post configuration.

    All content is managed in Git. Every change is tracked, drafts can be handled through branches, and rollbacks are straightforward. This replaces WordPress revisions with a clearer and more reliable workflow.

    This keeps the workflow simple while giving us full control over structure, formatting, and how content is rendered. In practice, this acts as a lightweight headless CMS alternative without the overhead of managing another platform.

    The Content Team Is Now Self-Sufficient

    This was the shift we did not fully anticipate. Our content team, most of whom had never used the terminal before, can now manage the entire blog independently. They do not need a developer to publish a post, fix a typo, update metadata, or make structural changes.

    Claude Code bridges the gap between "I know what I want to write" and "the code is committed and deployed."

    This matters because it addresses a long-standing limitation of CMS-free setups. While static MDX blogs offer clear benefits in performance, security, and version control, they have traditionally been difficult for non-technical teams to work with. AI coding agents like Claude Code remove that barrier completely.

    The result is a content operation that is:

    This is what we mean when we say we are an AI-powered growth platform. We do not just build AI products for our clients. We use AI to run our own operations. The website you are reading right now was built, is maintained, and is published using AI at every step.

    Who Should Consider This Migration

    This approach works really well for some teams, but not all. For the right team, this can be a strong WordPress alternative for SaaS, especially when performance and flexibility matter.

    Good Fit:

    Not Ideal For:

    Final Thoughts

    This migration was not just about improving performance. It was about building a system that aligns with how discovery is changing.

    As AI platforms like ChatGPT and Perplexity become primary entry points to the web, content needs to be structured, accessible, and easy for these systems to understand. That is where Generative Engine Optimization (GEO) becomes critical. If you want to know which tools can help, see our guide on the best tools to rank in ChatGPT and AI search engines.

    By moving to a simpler, structured, and static setup, we made our content easier to serve, easier to maintain, and easier to surface in AI-driven environments.

    At Botric, this is exactly what we focus on, helping businesses improve their content visibility across AI-driven search by strengthening their GEO and building systems designed for modern discovery.

    Try Botric for free today to see how your content performs across AI-driven search and where you can improve.

    FAQ: Common Questions Before You Migrate

    1. Will we lose SEO rankings during the migration?

    Not if redirects are handled correctly. We generate a redirects.json file that maps every old WordPress URL to its new path using permanent (301) redirects. This preserves link equity, prevents broken links, and signals to search engines that the content has moved. In our case, we did not see any meaningful drop in rankings.

    2. What if our content team cannot work with Git?

    This was our biggest concern, but it turned out to be a non-issue. Our content team now manages the blog independently, publishing posts and making updates without developer help. Claude Code bridges the gap by turning instructions into code changes, with a learning curve shorter than a typical CMS.

    3. Why not use a headless CMS like Sanity or Contentful?

    You can, and for many teams it is the right choice. A headless CMS makes sense if you need a visual editor, role-based workflows, or complex content modeling. For us, it would have added another system to manage along with additional cost. Since our workflow is file-based and version-controlled, we chose to keep things simpler.

    4. Does Next.js static export support dynamic features?

    Static export works best when content is known at build time. If your site depends on user authentication, real-time data, or server-side personalization, you will need a server-backed setup or external APIs. Our Next.js marketing website is purely informational, which makes static export a good fit.

    5. How long does the migration take?

    In our case, it took a few days. Without automation, a more typical timeline for a small team would be a couple of weeks, including rebuilding pages, handling SEO, testing, and deployment setup. The content migration itself can be automated and completed quickly once the script is in place.

    Tagged with
    #wordpress to nextjs migration#Next.js marketing website#Headless CMS alternative#WordPress alternative for SaaS

    Continue reading

    Related articles