Headless WordPress built with the Next.js App Router and React Server Components
See the codeA modern headless WordPress starter built with Next.js 16, React 19, and TypeScript.
Live Demo | Video Tutorial | Headless Theme (761)
Need a WooCommerce version? Try next-woo
# Clone the repository
git clone https://github.com/9d8dev/next-wp.git
cd next-wp
# Install dependencies
pnpm install
# Set up environment variables
cp .env.example .env.local
# Edit .env.local with your WordPress URL and credentials
# Start development server
pnpm dev
Your site is now running at http://localhost:3000.
packageManager field — enable with corepack enable)Create a .env.local file in the root directory:
WORDPRESS_URL="https://your-wordpress-site.com" # Full WordPress URL
WORDPRESS_HOSTNAME="your-wordpress-site.com" # Domain for image optimization
WORDPRESS_WEBHOOK_SECRET="your-secret-key-here" # Secret for cache revalidation
next-wp/
├── __tests__/ # Vitest test suite
│ ├── api/ # API route tests
│ └── lib/ # Library tests
├── app/ # Next.js App Router
│ ├── api/
│ │ ├── og/ # OG image generation
│ │ └── revalidate/ # Cache revalidation webhook
│ ├── pages/[slug]/ # Dynamic WordPress pages
│ ├── posts/
│ │ ├── [slug]/ # Individual post pages
│ │ ├── authors/ # Author archive
│ │ ├── categories/ # Category archive
│ │ └── tags/ # Tag archive
│ ├── layout.tsx # Root layout
│ ├── page.tsx # Homepage
│ └── sitemap.ts # Dynamic sitemap
├── components/
│ ├── posts/ # Post-related components
│ │ ├── post-card.tsx # Post card component
│ │ ├── filter.tsx # Filter controls
│ │ └── search-input.tsx # Search component
│ ├── nav/ # Navigation components
│ ├── theme/ # Theme toggle
│ └── ui/ # shadcn/ui components
├── lib/
│ ├── wordpress.ts # WordPress API functions
│ └── wordpress.d.ts # TypeScript definitions
├── plugin/ # WordPress revalidation plugin
├── menu.config.ts # Navigation configuration
├── site.config.ts # Site metadata
└── vitest.config.ts # Test configuration
Railway deploys the complete stack with one click: MySQL + WordPress + Next.js.
The Railway template uses a custom WordPress Docker image (ghcr.io/9d8dev/next-wp-wordpress) with:
┌─────────┐ ┌───────────┐ ┌─────────┐
│ MySQL │────▶│ WordPress │◀────│ Next.js │
│ DB │ │ (CMS) │ │(Frontend)│
└─────────┘ └───────────┘ └─────────┘
1. Complete WordPress Installation
https://wordpress-xxx.up.railway.app)2. Configure the Revalidation Plugin
The next-revalidate plugin is pre-installed and activated.
https://next-wp-xxx.up.railway.app)WORDPRESS_WEBHOOK_SECRET value3. Test the Setup
By default, the template deploys from the 9d8dev/next-wp repository. To customize:
Railway creates a copy of the repository in your GitHub. You can then:
WORDPRESS_URL - Your existing WordPress site URLWORDPRESS_HOSTNAME - WordPress domain (for images)WORDPRESS_WEBHOOK_SECRET - Generate a secure random string# Install dependencies
pnpm install
# Copy environment template
cp .env.example .env.local
# Configure your WordPress connection in .env.local
# Then start the dev server
pnpm dev
Required: Your WordPress site must have the REST API enabled (default since WP 4.7).
All WordPress interactions are centralized in lib/wordpress.ts:
getRecentPosts(filters?) // Recent posts (max 100)
getPostsPaginated(page, perPage, filters?) // Paginated posts with headers
getPostBySlug(slug) // Single post by slug (with _embed)
getPostById(id) // Single post by ID
getAllPostSlugs() // All slugs (for static generation)
getAllPostsForSitemap() // All slugs + modified dates
getAllCategories() // All categories
getCategoryById(id) // Category by ID
getCategoryBySlug(slug) // Category by slug
getAllTags() // All tags
getTagById(id) // Tag by ID
getTagBySlug(slug) // Tag by slug
getPostsByCategory(id) // Posts in category
getPostsByTag(id) // Posts with tag
getTagsByPost(postId) // Tags on a post
getAllAuthors() // All authors
getAuthorById(id) // Author by ID
getAuthorBySlug(slug) // Author by slug
getPostsByAuthor(id) // Posts by author
getAllPages() // All pages
getPageById(id) // Page by ID
getPageBySlug(slug) // Page by slug
getPostsByCategoryPaginated(categoryId, page, perPage)
getPostsByTagPaginated(tagId, page, perPage)
getPostsByAuthorPaginated(authorId, page, perPage)
searchCategories(query) // Search categories
searchTags(query) // Search tags
searchAuthors(query) // Search authors
import { getPostsPaginated } from "@/lib/wordpress";
const { data: posts, headers } = await getPostsPaginated(1, 9, {
category: "news",
search: "nextjs"
});
console.log(`Found ${headers.total} posts across ${headers.totalPages} pages`);
The starter uses Next.js cache tags for efficient revalidation:
WORDPRESS_WEBHOOK_SECRET valueWhen content changes in WordPress, only affected pages are revalidated.
Note: If using the Railway template, the plugin is pre-installed automatically.
Edit site.config.ts for site metadata:
export const siteConfig = {
site_name: "Your Site",
site_domain: "yourdomain.com",
site_description: "Your site description"
};
Edit menu.config.ts for navigation links:
export const mainMenu = {
home: "/",
blog: "/posts",
// Add more links...
};
export const contentMenu = {
categories: "/posts/categories",
tags: "/posts/tags",
authors: "/posts/authors",
};
This project uses shadcn/ui with Tailwind CSS. Customize colors in your CSS or update the shadcn theme.
your-site.com/wp-json/wp/v2/postsWORDPRESS_HOSTNAMEnext.config.ts has the correct remotePatternsWORDPRESS_WEBHOOK_SECRET matches in both WordPress and Next.js/api/revalidateThe project uses Vitest for unit testing.
pnpm test # Run all tests
pnpm test:watch # Run in watch mode
Tests cover the core modules:
| Module | What's tested |
|---|---|
lib/utils | cn() class merging with Tailwind deduplication |
lib/metadata | stripHtml, truncateHtml, OG/Twitter metadata generation |
lib/wordpress | API fetch layer, pagination, error handling, graceful fallbacks |
api/revalidate | Webhook secret validation, content type routing, cache revalidation |
pnpm dev # Start development server
pnpm build # Build for production
pnpm start # Start production server
pnpm lint # Run ESLint
pnpm test # Run tests
Contributions are welcome! Please:
git checkout -b feature/amazing-feature)git commit -m 'Add amazing feature')git push origin feature/amazing-feature)MIT License - see LICENSE for details.
Built with Next.js, Tailwind CSS, shadcn/ui, and brijr/craft.
Created by Bridger Tower and Cameron Youngblood at 9d8.
729 followers · starred Jun 2024
112 followers · starred Nov 2024
124 followers · starred Jun 2024
298 followers · starred Dec 2024
Headless WordPress built with the Next.js App Router and React Server Components
See the codeA modern headless WordPress starter built with Next.js 16, React 19, and TypeScript.
Live Demo | Video Tutorial | Headless Theme (761)
Need a WooCommerce version? Try next-woo
# Clone the repository
git clone https://github.com/9d8dev/next-wp.git
cd next-wp
# Install dependencies
pnpm install
# Set up environment variables
cp .env.example .env.local
# Edit .env.local with your WordPress URL and credentials
# Start development server
pnpm dev
Your site is now running at http://localhost:3000.
packageManager field — enable with corepack enable)Create a .env.local file in the root directory:
WORDPRESS_URL="https://your-wordpress-site.com" # Full WordPress URL
WORDPRESS_HOSTNAME="your-wordpress-site.com" # Domain for image optimization
WORDPRESS_WEBHOOK_SECRET="your-secret-key-here" # Secret for cache revalidation
next-wp/
├── __tests__/ # Vitest test suite
│ ├── api/ # API route tests
│ └── lib/ # Library tests
├── app/ # Next.js App Router
│ ├── api/
│ │ ├── og/ # OG image generation
│ │ └── revalidate/ # Cache revalidation webhook
│ ├── pages/[slug]/ # Dynamic WordPress pages
│ ├── posts/
│ │ ├── [slug]/ # Individual post pages
│ │ ├── authors/ # Author archive
│ │ ├── categories/ # Category archive
│ │ └── tags/ # Tag archive
│ ├── layout.tsx # Root layout
│ ├── page.tsx # Homepage
│ └── sitemap.ts # Dynamic sitemap
├── components/
│ ├── posts/ # Post-related components
│ │ ├── post-card.tsx # Post card component
│ │ ├── filter.tsx # Filter controls
│ │ └── search-input.tsx # Search component
│ ├── nav/ # Navigation components
│ ├── theme/ # Theme toggle
│ └── ui/ # shadcn/ui components
├── lib/
│ ├── wordpress.ts # WordPress API functions
│ └── wordpress.d.ts # TypeScript definitions
├── plugin/ # WordPress revalidation plugin
├── menu.config.ts # Navigation configuration
├── site.config.ts # Site metadata
└── vitest.config.ts # Test configuration
Railway deploys the complete stack with one click: MySQL + WordPress + Next.js.
The Railway template uses a custom WordPress Docker image (ghcr.io/9d8dev/next-wp-wordpress) with:
┌─────────┐ ┌───────────┐ ┌─────────┐
│ MySQL │────▶│ WordPress │◀────│ Next.js │
│ DB │ │ (CMS) │ │(Frontend)│
└─────────┘ └───────────┘ └─────────┘
1. Complete WordPress Installation
https://wordpress-xxx.up.railway.app)2. Configure the Revalidation Plugin
The next-revalidate plugin is pre-installed and activated.
https://next-wp-xxx.up.railway.app)WORDPRESS_WEBHOOK_SECRET value3. Test the Setup
By default, the template deploys from the 9d8dev/next-wp repository. To customize:
Railway creates a copy of the repository in your GitHub. You can then:
WORDPRESS_URL - Your existing WordPress site URLWORDPRESS_HOSTNAME - WordPress domain (for images)WORDPRESS_WEBHOOK_SECRET - Generate a secure random string# Install dependencies
pnpm install
# Copy environment template
cp .env.example .env.local
# Configure your WordPress connection in .env.local
# Then start the dev server
pnpm dev
Required: Your WordPress site must have the REST API enabled (default since WP 4.7).
All WordPress interactions are centralized in lib/wordpress.ts:
getRecentPosts(filters?) // Recent posts (max 100)
getPostsPaginated(page, perPage, filters?) // Paginated posts with headers
getPostBySlug(slug) // Single post by slug (with _embed)
getPostById(id) // Single post by ID
getAllPostSlugs() // All slugs (for static generation)
getAllPostsForSitemap() // All slugs + modified dates
getAllCategories() // All categories
getCategoryById(id) // Category by ID
getCategoryBySlug(slug) // Category by slug
getAllTags() // All tags
getTagById(id) // Tag by ID
getTagBySlug(slug) // Tag by slug
getPostsByCategory(id) // Posts in category
getPostsByTag(id) // Posts with tag
getTagsByPost(postId) // Tags on a post
getAllAuthors() // All authors
getAuthorById(id) // Author by ID
getAuthorBySlug(slug) // Author by slug
getPostsByAuthor(id) // Posts by author
getAllPages() // All pages
getPageById(id) // Page by ID
getPageBySlug(slug) // Page by slug
getPostsByCategoryPaginated(categoryId, page, perPage)
getPostsByTagPaginated(tagId, page, perPage)
getPostsByAuthorPaginated(authorId, page, perPage)
searchCategories(query) // Search categories
searchTags(query) // Search tags
searchAuthors(query) // Search authors
import { getPostsPaginated } from "@/lib/wordpress";
const { data: posts, headers } = await getPostsPaginated(1, 9, {
category: "news",
search: "nextjs"
});
console.log(`Found ${headers.total} posts across ${headers.totalPages} pages`);
The starter uses Next.js cache tags for efficient revalidation:
WORDPRESS_WEBHOOK_SECRET valueWhen content changes in WordPress, only affected pages are revalidated.
Note: If using the Railway template, the plugin is pre-installed automatically.
Edit site.config.ts for site metadata:
export const siteConfig = {
site_name: "Your Site",
site_domain: "yourdomain.com",
site_description: "Your site description"
};
Edit menu.config.ts for navigation links:
export const mainMenu = {
home: "/",
blog: "/posts",
// Add more links...
};
export const contentMenu = {
categories: "/posts/categories",
tags: "/posts/tags",
authors: "/posts/authors",
};
This project uses shadcn/ui with Tailwind CSS. Customize colors in your CSS or update the shadcn theme.
your-site.com/wp-json/wp/v2/postsWORDPRESS_HOSTNAMEnext.config.ts has the correct remotePatternsWORDPRESS_WEBHOOK_SECRET matches in both WordPress and Next.js/api/revalidateThe project uses Vitest for unit testing.
pnpm test # Run all tests
pnpm test:watch # Run in watch mode
Tests cover the core modules:
| Module | What's tested |
|---|---|
lib/utils | cn() class merging with Tailwind deduplication |
lib/metadata | stripHtml, truncateHtml, OG/Twitter metadata generation |
lib/wordpress | API fetch layer, pagination, error handling, graceful fallbacks |
api/revalidate | Webhook secret validation, content type routing, cache revalidation |
pnpm dev # Start development server
pnpm build # Build for production
pnpm start # Start production server
pnpm lint # Run ESLint
pnpm test # Run tests
Contributions are welcome! Please:
git checkout -b feature/amazing-feature)git commit -m 'Add amazing feature')git push origin feature/amazing-feature)MIT License - see LICENSE for details.
Built with Next.js, Tailwind CSS, shadcn/ui, and brijr/craft.
Created by Bridger Tower and Cameron Youngblood at 9d8.
729 followers · starred Jun 2024
112 followers · starred Nov 2024
124 followers · starred Jun 2024
298 followers · starred Dec 2024