Initializing
Back to Projects
Year2026
DomainFrontend
AccessPrivate Repo
Complexity7.8 / 10
ScreenshotsArchitecture Docs
Next.js 14React 18TypeScriptGraphQLCSS ModulesWordPress APINode.jsDocker
Frontendwip

NGO-3 Website Migration — WordPress to Next.js Headless Conversion

A complete, production-grade migration of a legacy WordPress NGO portal into a high-performance headless Next.js 14 frontend using CSS Modules.

Lighthouse Performance0+
Static Pages Pre-built0+
Data Build Speedup0x Faster
Load Time Reduction0%

Table of Contents


The Challenge

The digital interface of NGO-3, a prominent non-profit organization, was built on an aging, monolithic WordPress system. Over time, the installation became clogged with third-party plugins, database overhead, and heavy layout themes. The result was severe page bloat, slow page load speeds exceeding 6 seconds on mobile, and constant security vulnerabilities that required frequent patching.

The primary objective of the migration was to extract all content, program records, and historical articles from WordPress and feed them into a blazing-fast, modern, headless frontend. The new platform had to achieve near-perfect Lighthouse performance scores to prevent user bounce, while keeping the client's editing team entirely inside their familiar WordPress admin dashboard.

The requirement: Refactor and migrate the monolithic WordPress platform into a headless, secure Next.js 14 application using CSS Modules. Ensure perfect core web vitals and dynamic incremental data synchronization during static site generation.


Architecture & Solution

We architected a headless CMS structure. The existing WordPress installation serves solely as a content API exposed via WPGraphQL. During build time, Next.js queries this GraphQL endpoint to generate fully optimized, static pages distributed at the edge.

Parsing system architecture diagram...

Decoupled Data Pipeline

LayerSystemResponsibility
Headless CMSWordPress + WPGraphQLContent editing, image media library storage, and taxonomies management.
Next.js CoreNext.js 14 App RouterIncremental static build compiles, Server-Side components, and API routing.
StylingVanilla CSS ModulesClean, structured layouts without bloated class definitions.

Tech Stack

LayerTechnologyRole
FrameworkNext.js 14 (App Router)Static Site Generation (SSG) with On-Demand Incremental Static Regeneration (ISR).
UI LibraryReact 18Declarative component generation.
LanguageTypeScriptStrong typings for API response schemas and component props.
Data IngestionApollo Client / GraphQLBatch querying WordPress custom post types.
StylingCSS ModulesLocalized, high-performance styling eliminating CSS collisions.
DeploymentDockerContainerized runner for running Next.js dynamically in production.

Key Engineering Decisions

1. Incremental Static Regeneration (ISR) with Webhook Sync

To prevent content editors from having to trigger manual site builds, we designed a webhook receiver endpoint in Next.js. This endpoint accepts updates from WordPress and programmatically triggers page revalidation in milliseconds.

typescript
// src/app/api/revalidate/route.ts
import { NextRequest, NextResponse } from 'next/server';
import { revalidatePath } from 'next/cache';

export async function POST(req: NextRequest) {
  try {
    const { slug, type } = await req.json();
    if (!slug) {
      return NextResponse.json({ message: 'Missing slug parameter' }, { status: 400 });
    }

    const targetPath = type === 'blog' ? `/blog/${slug}` : `/${slug}`;
    revalidatePath(targetPath);
    return NextResponse.json({ revalidated: true, path: targetPath, now: Date.now() });
  } catch (err: any) {
    return NextResponse.json({ message: 'Internal revalidation error', error: err.message }, { status: 500 });
  }
}

2. Batch-Queried GraphQL Ingestion

WordPress relationships can easily create N+1 query bottlenecks during static builds. We designed batched query routines to compile hundreds of pages in a single trip.

typescript
// src/lib/wp-graphql.ts
import { ApolloClient, InMemoryCache, gql } from '@apollo/client';

const client = new ApolloClient({
  uri: process.env.WP_GRAPHQL_ENDPOINT,
  cache: new InMemoryCache(),
  defaultOptions: {
    query: {
      fetchPolicy: 'no-cache', // Prevents stale builds in server context
    },
  },
});

export async function fetchAllPostSlugs() {
  const { data } = await client.query({
    query: gql`
      query GetPostSlugs {
        posts(first: 100) {
          nodes {
            slug
          }
        }
      }
    `,
  });
  return data.posts.nodes.map((node: { slug: string }) => node.slug);
}

3. Modular Theme Custom Properties

To maintain perfect brand alignment with the legacy NGO design, we translated the brand system into highly optimized CSS custom variables within a root tokens stylesheet.

css
/* src/styles/tokens.module.css */
:root {
  --color-primary: #1b4d3e;     /* NGO forest green */
  --color-secondary: #f4a261;   /* Warm accent gold */
  --color-background: #fafaf9;  /* Soft cream canvas */
  --color-text: #1c1917;        /* Highly readable stone gray */

  --font-family-display: 'Lora', Georgia, serif;
  --font-family-body: 'Plus Jakarta Sans', sans-serif;
  
  --spacing-grid: 16px;
  --border-radius-card: 12px;
}

4. Exponential Backoff API Fetch Wrapper

Monolithic WordPress servers are prone to connection timeouts under high build load. We introduced a wrapper to automatically retry transient failed fetches with exponential delay.

typescript
// src/shared/utils/fetch-retry.ts
export async function fetchWithRetry(url: string, options: RequestInit, retries = 3, delay = 1000): Promise<Response> {
  try {
    const res = await fetch(url, options);
    if (!res.ok && retries > 0) throw new Error(`HTTP Error ${res.status}`);
    return res;
  } catch (err) {
    if (retries === 0) throw err;
    console.warn(`Fetch failed. Retrying in ${delay}ms... (${retries} left)`);
    await new Promise((resolve) => setTimeout(resolve, delay));
    return fetchWithRetry(url, options, retries - 1, delay * 2);
  }
}

Deployment

Quick Start

Run the Next.js development server:

bash
# Clone the repository and configure env files
cp .env.example .env.local

# Install monorepo dependencies
pnpm install

# Start the Next.js development router
pnpm dev

Local Dev Ports

ServicePort
Next.js Web PortalPORT_WEB (3005)
WPGraphQL Endpointhttp://localhost:8080/graphql

Architecture Feedback

Spotted a potential optimization or antipattern? Let me know.

Submit a Technical Suggestion