Build Phases
Phase 0 — WordPress Setup
Do these steps before writing any Next.js code.
Set Permalinks — Do This First
Go to Settings → Permalinks, select Post name (/%postname%/), click Save.
Without Post name permalinks, the REST API returns 404 for everything. Always set this first.
Test the REST API
Open your browser and visit these URLs. You should see a JSON array:
https://yourdomain.com/wp-json/wp/v2/posts
https://yourdomain.com/wp-json/wp/v2/pagesInstall Required Plugins
Go to Plugins → Add New and install these:
| Plugin | Search For |
|---|---|
| Headless Mode | Headless Mode WP Engine |
| Advanced Custom Fields | Advanced Custom Fields |
| Contact Form 7 | Contact Form 7 |
| JWT Authentication | JWT Authentication for WP REST API |
| Yoast SEO | Yoast SEO |
Register Custom Post Types (CPTs)
Add to functions.php for any content beyond standard Posts:
function register_projects_cpt() {
register_post_type('projects', array(
'labels' => array('name' => 'Projects', 'singular_name' => 'Project'),
'public' => true,
'has_archive' => true,
'show_in_rest' => true, // CRITICAL: enables REST API
'rest_base' => 'projects',
'supports' => array('title', 'editor', 'thumbnail', 'excerpt', 'custom-fields'),
));
}
add_action('init', 'register_projects_cpt');show_in_rest => true is mandatory. Without it, your CPT will be invisible to the REST API.
Add ACF Fields
Go to Custom Fields → Add New, create fields, set Location to your CPT. In field group settings, enable "Show in REST API". Test with:
https://yourdomain.com/wp-json/wp/v2/projects?_embed&per_page=1
// Look for: "acf": { "your_field": "value" }Configure CORS Headers
Add to functions.php so your Next.js frontend can fetch from WordPress:
function add_cors_headers() {
header("Access-Control-Allow-Origin: https://yourdomain.com");
header("Access-Control-Allow-Methods: GET, POST, OPTIONS");
header("Access-Control-Allow-Headers: Content-Type, Authorization");
if ($_SERVER['REQUEST_METHOD'] === 'OPTIONS') {
status_header(200); exit();
}
}
add_action('init', 'add_cors_headers');During local development, temporarily change https://yourdomain.com to * to allow all origins. Lock it down before going live.