WordPress to Next.js Conversion Guide
Standard Operating Procedure for FFC Site Conversions
This guide documents the complete process for converting a WordPress/Divi charity website to a Next.js static site deployed on GitHub Pages. It serves as both an SOP for FFC administrators and an educational resource for charities evaluating the FFC technology stack.
Table of Contents
1. Why FFC Migrates Off WordPress
The case for static sites over WordPress for nonprofits
Free For Charity made the strategic decision to migrate all supported charity websites from WordPress/Divi to Next.js static sites. This section explains the reasoning for charities and administrators who may be unfamiliar with the trade-offs.
WordPress Challenges
- βHosting costs: WPMUDEV hosting runs $50-100+/month per site with cPanel infrastructure
- βSecurity surface: PHP runtime, database, admin panel, and plugins each present attack vectors requiring constant patching
- βPlugin dependencies: Divi theme builder, Hummingbird, Smush Pro, SmartCrawl, Beehive Analytics all require updates and licensing
- βPerformance overhead: Dynamic page generation, database queries, and plugin JavaScript increase load times
- βMaintenance burden: Core updates, plugin updates, PHP version upgrades, database backups, and security monitoring require ongoing effort
Static Site Advantages
- βFree hosting: GitHub Pages is completely free with unlimited bandwidth for public repos
- βZero attack surface: No server, no database, no admin panel, no PHP. Only static HTML/CSS/JS files
- βNo dependencies: No plugins to update, no licenses to renew, no hosting provider to manage
- βBlazing performance: Pre-built HTML served from GitHub/Cloudflare edge. Lighthouse scores of 90+ are standard
- βGit-based workflow: Version control, pull requests, automated testing, and CI/CD provide professional-grade development practices
Bottom line: For a typical charity website (informational content, donation links, contact forms), WordPress is overkill. A static site delivers better performance, security, and cost savings while requiring less maintenance. The content rarely changes, and when it does, a git commit and automated deployment handles it.
2. The FFC Technology Stack
What powers every FFC-supported charity website
| Layer | Technology | Purpose | Cost |
|---|---|---|---|
| Framework | Next.js 16 (App Router) | React-based static site generator with TypeScript | Free |
| Styling | Tailwind CSS 4 | Utility-first CSS framework for responsive design | Free |
| Hosting | GitHub Pages | Static file hosting with custom domain + HTTPS | Free |
| DNS + DDoS | Cloudflare | DNS management and DDoS protection (DNS-only mode, no CDN caching) | Free |
| CI/CD | GitHub Actions | Automated build, test, security scan, deploy, and Lighthouse audit | Free |
| Source Control | GitHub | Version control, issue tracking, pull requests | Free |
| Testing | Jest + Playwright + Lighthouse CI | Unit tests, E2E tests, accessibility, performance audits | Free |
| Code Quality | ESLint + Prettier + Husky + commitlint | Linting, formatting, pre-commit hooks, conventional commits | Free |
Total ongoing cost: $0/month. The entire stack is free for public repositories. Compare this to $50-100+/month for WordPress hosting. For a charity with 10 websites, that is $6,000-12,000/year saved that goes directly back to the charitable mission.
3. Prerequisites and Tools
What you need before starting a conversion
Required Software
- 1.Node.js 20.x+ (LTS) β JavaScript runtime
- 2.pnpm (preferred) or npm β Package manager
- 3.Git β Version control
- 4.GitHub CLI (gh) β Repository and issue management
- 5.VS Code β Recommended editor with TypeScript support
Required Access
- 1.WordPress admin for the source site (to install Simply Static Pro)
- 2.GitHub org membership in FreeForCharity (to create repos)
- 3.Cloudflare dashboard access for the charity domain
- 4.WPMUDEV/cPanel access for the existing WordPress host
FFC Repo Naming Convention
All FFC repositories follow a strict naming pattern:
FFC-EX-DomainName.comβ External charity sites (e.g.,FFC-EX-AllTypeTowing.com)FFC-IN-DomainName.orgβ Internal/platform FFC sites (e.g.,FFC-IN-ffcadmin.org)
4. Content Audit Process
Inventory everything before you start building
Before writing any code, perform a thorough audit of the existing WordPress site. This ensures no content is lost and provides a clear scope for the conversion.
4.1 β Query the WordPress REST API
WordPress exposes all content via its REST API. Use these endpoints to build a complete inventory:
# Get all pages
curl "https://DOMAIN/wp-json/wp/v2/pages?per_page=100&_fields=id,title,slug,status,link,modified"
# Get all posts
curl "https://DOMAIN/wp-json/wp/v2/posts?per_page=100&_fields=id,title,slug,status,link,modified"
# Get all media
curl "https://DOMAIN/wp-json/wp/v2/media?per_page=100&_fields=id,title,source_url,mime_type,media_details"
Save results to a CSV file for tracking. If more than 100 items exist, paginate with &page=2.
4.2 β Cross-Reference with Sitemap
Check https://DOMAIN/sitemap.xml and compare against your REST API inventory. Sometimes pages exist in the sitemap but not the API (or vice versa).
4.3 β Create URL Mapping Document
Map every WordPress URL to its Next.js equivalent. Categorize each page:
- MATCHED: WordPress slug matches an existing Next.js route
- TO BUILD: Page exists in WordPress but not yet in Next.js
- DROP: Page is not needed (test pages, WordPress defaults, transactional pages)
- REDIRECT: Slug changed or page dropped; needs 301 redirect
4.4 β Media Asset Inventory
Catalog all images from wp-content/uploads/. Cross-reference against assets already in the Next.js repo. Identify images to migrate, duplicates to remove, and files to convert to WebP format.
4.5 β Content Ownership Matrix
Document where every piece of content lives in the Next.js repo: which pages use JSON data files, which have inline content in components, and what the update procedure is for each type.
Important: Freeze all WordPress content edits after the audit. Any changes made to WordPress after this point may be lost in the conversion.
5. Simply Static Export
Extracting a static HTML snapshot from WordPress
Simply Static Pro is a WordPress plugin that generates a complete static HTML snapshot of your site. FFC uses this as a reference for content extraction, not as the final deployment. The actual Next.js site is built from components, not from Simply Static output.
Step 1: Pre-Export Checklist
Disable these WordPress plugins before running Simply Static to avoid interference:
- Hummingbird (performance/caching)
- Smush Pro (image optimization)
- Beehive Analytics (tracking)
- SmartCrawl SEO (may inject dynamic scripts)
In Enhanced Crawl > Plugins to Include, remove all admin-only and token-bearing plugins. Only keep Simply Static, Simply Static Pro, and plugins with actual frontend output (e.g., Strong Testimonials). Specifically remove:
- Tawk.to Live Chat β embeds API tokens that trigger GitHub push protection
- Microsoft Clarity β embeds project ID tokens
- Defender Pro β ships webauthn/JWT JS with secret-like patterns
- Hustle Pro, Shipper Pro, Snapshot Pro, Broken Link Checker, Branda Pro, Divi Dash, WPMU DEV Dashboard β admin-only, no frontend assets
Also clear the Divi Builder cache: Divi > Theme Options > Builder > Advanced > Static CSS File Generation > Clear
Step 2: Configure Simply Static Pro
Navigate to Simply Static > Settings > Deploy and configure:
| Setting | Value |
|---|---|
| Deployment Method | GitHub |
| Organization | FreeForCharity |
| Repository | FFC-EX-DomainName.com (or FFC-IN-...) |
| Branch | simply-static-export |
| Folder | (leave empty for root) |
The export goes to a simply-static-export branch, never to main. Create this branch before running the export:
Step 3: Run Diagnostics and Export
Run Simply Static > Diagnostics first to verify no issues. Then generate the static export. The plugin will push the static HTML to your GitHub repo branch. This typically takes 5-15 minutes depending on site size.
Step 4: GitHub Push Protection Fix (Required)
GitHub enables secret scanning push protection on all public repositories by default. WordPress HTML pages often contain embedded tokens (tawk.to widget IDs, Google Maps API keys, reCAPTCHA site keys, analytics IDs) that trigger GitHub's scanner. When Simply Static tries to commit a batch of files containing a flagged pattern, the entire batch is silently dropped. The plugin still reports βCommitted X of Y filesβ and marks the export as complete, but the HTML pages never reach the branch.
Additionally, pushing 6,000+ files via the GitHub Trees API in rapid succession triggers secondary rate limiting, which causes further batch failures.
Fix: Create an org-level branch ruleset that bypasses push protection
- Go to GitHub Org Settings > Rulesets > New ruleset
- Name:
FFC Simply Static Export Bypass - Enforcement: Active
- Target: Branch, pattern:
simply-static-export - Bypass actors: Add Organization Admin with βAlwaysβ bypass
- Under Rules, enable Block force pushes (keep protection) but do NOT enable βRequire secret scanning resultsβ
Alternatively, use the GitHub CLI:
gh api orgs/FreeForCharity/rulesets \ --method POST \ -f name="FFC Simply Static Export Bypass" \ -f target=branch \ -f enforcement=active \ -f 'conditions[ref_name][include][]=refs/heads/simply-static-export' \ -f 'rules[][type]=secret_scanning' \ -f 'bypass_actors[][actor_type]=OrganizationAdmin' \ -f 'bypass_actors[][actor_id]=0' \ -f 'bypass_actors[][bypass_mode]=always'
Step 5: Enable Request Throttling
In Simply Static Pro settings, check βThrottle GitHub Requestsβ to prevent secondary rate limiting when committing large exports. This adds a small delay between API calls and dramatically reduces rate limit failures.
For sites with 5,000+ URLs, also consider excluding unnecessary directories in the URLs to Exclude field:
# Exclude admin-only and irrelevant plugin assets /wp-admin/ /wp-content/plugins/snapshot-backups/ /wp-content/plugins/shipper/ /wp-content/plugins/wp-defender/ /wp-content/plugins/hustle/ /wp-content/plugins/broken-link-checker/ /wp-content/plugins/ultimate-branding/
Step 6: Dedicated Fine-Grained PAT
Create a dedicated fine-grained Personal Access Token for Simply Static exports. Never reuse a broad-scoped PAT. Configure the token with:
- Repository access: Only selected repositories (the target repo)
- Permissions: Contents (Read and write) only
- Expiration: 90 days maximum, rotate regularly
- Name:
simply-static-REPONAMEfor easy identification
Never share, commit, or paste PATs in chat, issues, or documentation. If a PAT is exposed, revoke it immediately at github.com/settings/tokens.
Reference implementation: See the FFC-EX-AllTypeTowing.com repository for a completed Simply Static export example.
6. Template Setup and Customization
Starting from the FFC Single Page Template
Every new FFC site starts from the FFC_Single_Page_Template repository. This template includes the complete tech stack, CI/CD pipelines, testing infrastructure, and mandatory FFC components (footer, team section).
# 1. Create new repo from template (do NOT fork β clean history)
gh repo create FreeForCharity/FFC-EX-CharityName.com --public
# 2. Clone and copy template files
git clone https://github.com/FreeForCharity/FFC_Single_Page_Template.git template
git clone https://github.com/FreeForCharity/FFC-EX-CharityName.com.git site
cp -r template/* site/
# 3. Install and verify
cd site && pnpm install && pnpm build
Customization Checklist
8. Mandatory Team Section
Required for ALL FFC-supported charity websites
Every FFC site must display the organization's leadership team using the standardizedTeamMemberCard component.
Component Specifications
- Photo: 300x300px, circular crop,
ring-4white border with shadow - Format: WebP (not PNG or JPEG)
- Name: Full name displayed below photo
- Title: Role/position in the organization
- LinkedIn: SVG icon button linking to profile
Grid Layout (Responsive)
- Desktop: 3 columns (first row), 2 columns (second row if 5 members)
- Tablet: 2 columns
- Mobile: 1 column (stacked)
- Anchor:
id="team"for direct linking
Data Structure
Team member data lives in src/data/team/ as individual JSON files:
9. Divi-to-Tailwind Component Mapping
Converting WordPress/Divi modules to React/Tailwind components
Divi uses "modules" for layout. Each module maps to a Next.js/Tailwind component:
| Divi Module | Next.js/Tailwind Equivalent | Notes |
|---|---|---|
| Section + Row + Column | <section> with Tailwind grid/flex | Use max-w-7xl mx-auto for container |
| Fullwidth Header | HeroSection component | Background image + overlay + CTA |
| Blurb Module | CallToActionCard | Icon + title + description card |
| Accordion / Toggle | FAQ component with useState | Expand/collapse with plus/minus SVGs |
| Testimonial Module | Swiper carousel or grid | Quote marks + author + photo |
| Contact Form | External service (Formspree, etc.) | Static sites cannot process forms server-side |
| Number Counter | Animated counter with Framer Motion | Trigger on scroll/viewport intersection |
| Gallery Module | CSS Grid + WebP images | Responsive grid with object-cover |
| Video Module | <video> element with poster | Self-hosted MP4 in public/videos/ |
| CTA Module | Tailwind button/card with Link | Use Next.js Link for internal routes |
10. CI/CD Pipeline
Automated build, test, scan, and deploy workflows
The FFC template includes 4 GitHub Actions workflows that run automatically:
CI Workflow (ci.yml)
Runs on every push and pull request. Installs dependencies, runs linting (ESLint), type checking (TypeScript), unit tests (Jest), and builds the static site. Must pass before merge.
CodeQL Analysis (codeql.yml)
GitHub's security scanner. Runs after CI passes. Scans for JavaScript/TypeScript vulnerabilities, injection risks, and insecure patterns.
Deploy (deploy.yml)
Runs after CodeQL passes (on main branch only). Builds the production static site and deploys to GitHub Pages. Updates the live website automatically.
Lighthouse CI (lighthouse.yml)
Runs after deploy completes. Audits the live site for Performance, Accessibility, Best Practices, and SEO. Target scores: Performance β₯ 90, Accessibility β₯ 95, Best Practices β₯ 90, SEO β₯ 90.
Pipeline flow: Push to main β CI (lint + type check + test + build) β CodeQL (security scan) β Deploy (GitHub Pages) β Lighthouse (performance audit)
11. Testing Checklist
Quality gates before deployment
Automated Tests
Manual Verification
12. Deployment and DNS Cutover
Zero-downtime switch from WordPress to Next.js
Step 1: Configure GitHub Pages
- Add
public/CNAMEwith the charity domain (e.g.,charityname.org) - Add
public/.nojekyll(empty file to disable Jekyll processing) - Enable GitHub Pages in repo Settings > Pages > Source: GitHub Actions
Step 2: Set Up Cloudflare Redirects
Configure Cloudflare Bulk Redirects for any changed or dropped URLs:
- Create a Bulk Redirect List in Cloudflare dashboard
- Add 301 redirects for each old URL β new URL
- Enable the Bulk Redirect Rule
Note: Cloudflare is used in DNS-only mode (orange cloud OFF for most records). No CDN caching rules, no Workers, no Page Rules are needed.
Step 3: Staging Test
Before touching DNS, deploy to the GitHub Pages default URL and run full verification. The site should be accessible at https://freeforcharity.github.io/REPO-NAME/
Step 4: DNS Cutover
# In Cloudflare DNS:
# 1. Change the A record (pointing to WordPress/cPanel IP)
DELETE A charityname.org β 66.45.234.13
# 2. Add CNAME to GitHub Pages
ADD CNAME charityname.org β freeforcharity.github.io
# 3. Set proxy status: DNS-only (gray cloud) for DDoS protection
# 4. Wait for GitHub HTTPS certificate provisioning (up to 1 hour)
# 5. Verify all pages at https://charityname.org
Important: Keep the WordPress site running for at least 2 weeks after cutover. This provides a rollback option if any issues are discovered. Do NOT cancel WordPress hosting until the Next.js site is verified and stable.
13. Rollback Procedure
How to revert to WordPress if needed
If critical issues are discovered after cutover, reverting to WordPress takes about 5 minutes:
- 1.In Cloudflare DNS, delete the CNAME record pointing to
freeforcharity.github.io - 2.Re-create the A record pointing to the WordPress/cPanel IP (e.g.,
66.45.234.13) - 3.Wait for DNS propagation (typically 5-30 minutes with Cloudflare)
- 4.Verify the WordPress site is accessible at the domain
- 5.Investigate and fix the Next.js issue before attempting cutover again
Why this works: The WordPress site is untouched during the entire conversion process. DNS is the only thing that changes. Reverting DNS restores WordPress immediately.
14. FAQ for Charities and Admins
Common questions about the conversion process
Q: Will our website look different after conversion?
The goal is to maintain the same visual appearance and content. The technology changes behind the scenes, but visitors should see the same website. Performance will typically improve significantly.
Q: How do we update content after the conversion?
Content is updated by editing files in the GitHub repository. For simple text changes, you can edit directly on GitHub.com. For more complex changes, clone the repo locally, make changes, and push. The site rebuilds and deploys automatically.
Q: What about our contact forms?
Static sites cannot process form submissions server-side. FFC uses external form services like Formspree or integrates with the charity's existing form provider. The form still works for visitors; it just submits to an external service instead of WordPress.
Q: What about donations?
Donation processing is handled by external payment platforms (Zeffy, PayPal, Stripe, etc.) that the charity already uses. The donation page links to these external services. No payment processing happens on the website itself.
Q: Is there any downtime during the switch?
With proper execution, there is zero downtime. The new site is fully deployed and tested before the DNS switch. DNS changes with Cloudflare propagate in minutes. WordPress stays running as a backup during the transition period.
Q: What does it cost us?
Nothing. The entire stack (hosting, DNS, CI/CD, source control) is free. FFC provides the conversion service at no cost. The only requirement is the mandatory footer attribution.
Q: Can we go back to WordPress if we don't like it?
Yes. FFC keeps the WordPress site running for at least 2 weeks after cutover. Reverting is a 5-minute DNS change. However, most charities prefer the new stack due to better performance and zero maintenance requirements.
Q: Do we need technical staff to manage the new site?
For basic content updates, no. Simple text changes can be made through GitHub's web interface. For more complex changes (adding pages, modifying layout), some familiarity with HTML/React is helpful. FFC volunteers are available to assist with changes.
15. Reference Links
All related repositories, documentation, and tools
FFC Repositories
- FFC_Single_Page_Template β Canonical template for all sites
- FFC-IN-freeforcharity.org β Main FFC website (conversion in progress)
- FFC-IN-ffcadmin.org β Admin portal (tracking issues)
- FFC-EX-AllTypeTowing.com β First Simply Static reference export
FFC Admin Documentation
- Documentation Center β All admin portal documentation
- Tech Stack Overview β Complete technology stack details
- Sites List β All managed charity domains and health status
- Testing Documentation β Test suites and quality assurance
Revision History
| Date | Version | Changes |
|---|---|---|
| February 2026 | 1.1 | Added GitHub push protection fix, request throttling, PAT security guidance, and URL exclusion list for Simply Static exports (Section 5, Steps 4-6) |
| February 2026 | 1.0 | Initial guide created during FreeForCharity.org conversion project |
This guide is maintained at FFC-IN-ffcadmin.org and published at ffcadmin.org/wordpress-to-nextjs-guide