next-wp is a Next.js application for Headless WordPress. Includes functions for fetching posts, categories, tags, pages, and featured media.
This is a starter template for building a Next.js application that fetches data from a WordPress site using the WordPress REST API. The template includes functions for fetching posts, categories, tags, authors, and featured media from a WordPress site and rendering them in a Next.js application.
next-wp
is built with Next.js 15, React, Typescript, Tailwind, shadcn/ui, and brijr/craft. It pairs nicely with brijr/components for a rapid development experience. Built by Cameron and Bridger at 9d8.
lib/wordpress.ts
-> Functions for fetching WordPress CMS via Rest API with cache tagslib/wordpress.d.ts
-> Type declarations for WP Rest APIcomponents/posts/post-card.tsx
-> Component and styling for postsapp/posts/filter.tsx
-> Component for handling filtering of posts/menu.config.ts
-> Site nav menu configuration for desktop and mobile/site.config.ts
-> Configuration for sitemap.ts
app/sitemap.ts
-> Dynamically generated sitemapapp/api/revalidate/route.ts
-> Webhook endpoint for content revalidationwordpress/next-revalidate/
-> WordPress plugin for automatic revalidationThe following environment variables are required in your .env.local
file:
WORDPRESS_URL="https://wordpress.com"WORDPRESS_HOSTNAME="wordpress.com"WORDPRESS_WEBHOOK_SECRET="your-secret-key-here"
You can find the example of .env.local
file in the .env.example
file (and in Vercel).
The lib/wordpress.ts
file contains a comprehensive set of functions for interacting with the WordPress REST API. Each function is optimized for Next.js 15's caching system and includes proper error handling.
// Default fetch options for all WordPress API callsconst defaultFetchOptions = {next: {tags: ["wordpress"],revalidate: 3600, // 1 hour cache},headers: {Accept: "application/json","Content-Type": "application/json",},};
getAllPosts(filterParams?: { author?: string; tag?: string; category?: string; })
: Fetches posts with optional filtering by author, tag, or category. Uses cache tags for efficient revalidation.
getPostById(id: number)
: Retrieves a specific post by ID with proper error handling.
getPostBySlug(slug: string)
: Fetches a post using its URL-friendly slug.
getAllCategories()
: Retrieves all categories with cache invalidation support.
getCategoryById(id: number)
: Gets a specific category with error handling.
getCategoryBySlug(slug: string)
: Fetches a category by its slug.
getPostsByCategory(categoryId: number)
: Gets all posts in a category, using proper cache tags.
getAllTags()
: Fetches all available tags.
getTagById(id: number)
: Retrieves a specific tag.
getTagBySlug(slug: string)
: Gets a tag by its slug.
getTagsByPost(postId: number)
: Fetches all tags associated with a post.
getPostsByTag(tagId: number)
: Gets all posts with a specific tag.
getAllPages()
: Retrieves all WordPress pages.
getPageById(id: number)
: Gets a specific page by ID.
getPageBySlug(slug: string)
: Fetches a page by its slug.
getAllAuthors()
: Fetches all WordPress authors.
getAuthorById(id: number)
: Gets a specific author.
getAuthorBySlug(slug: string)
: Retrieves an author by slug.
getPostsByAuthor(authorId: number)
: Gets all posts by a specific author.
getFeaturedMediaById(id: number)
: Retrieves featured media (images) with size information.All functions use the custom WordPressAPIError
class for consistent error handling:
class WordPressAPIError extends Error {constructor(message: string, public status: number, public endpoint: string) {super(message);this.name = "WordPressAPIError";}}
Each function supports Next.js 15's cache tags for efficient revalidation:
// Example cache configuration{next: {tags: ["wordpress", "posts", `post-${id}`],revalidate: 3600,}}
try {// Fetch posts with filteringconst posts = await getAllPosts({author: "123",category: "news",tag: "featured",});// Handle errors properly} catch (error) {if (error instanceof WordPressAPIError) {console.error(`API Error: ${error.message} (${error.status})`);}}
These functions are designed to work seamlessly with Next.js 15's App Router and provide proper TypeScript support through the types defined in wordpress.d.ts
.
The lib/wordpress.d.ts
file contains comprehensive TypeScript type definitions for WordPress entities. The type system is built around a core WPEntity
interface that provides common properties for WordPress content:
interface WPEntity {id: number;date: string;date_gmt: string;modified: string;modified_gmt: string;slug: string;status: "publish" | "future" | "draft" | "pending" | "private";link: string;guid: {rendered: string;};}
Key type definitions include:
Post
: Blog posts and articles (extends WPEntity
)Page
: Static pages (extends WPEntity
)Author
: User informationCategory
: Post categories (extends Taxonomy
)Tag
: Post tags (extends Taxonomy
)FeaturedMedia
: Media attachments (extends WPEntity
)RenderedContent
: For content with HTML renderingRenderedTitle
: For titles with HTML renderingTaxonomy
: Base interface for categories and tags
interface FilterBarProps {authors: Author[];tags: Tag[];categories: Category[];selectedAuthor?: Author["id"];selectedTag?: Tag["id"];selectedCategory?: Category["id"];onAuthorChange?: (authorId: Author["id"] | undefined) => void;onTagChange?: (tagId: Tag["id"] | undefined) => void;onCategoryChange?: (categoryId: Category["id"] | undefined) => void;}
interface MediaDetails {width: number;height: number;file: string;sizes: Record<string, MediaSize>;}interface MediaSize {file: string;width: number;height: number;mime_type: string;source_url: string;}
All types are designed to be:
The components/posts/post-card.tsx
file contains the PostCard
component, which is responsible for rendering a single post card in the application. Here's an overview of the component:
post
: A Post
object representing the WordPress post to be rendered.The component fetches the featured media, author, and category associated with the post using the getFeaturedMediaById
, getAuthorById
, and getCategoryById
functions from lib/wordpress.ts
.
It formats the post date using the toLocaleDateString
method with the specified options.
The component renders a link to the individual post page using the post's slug.
Inside the link, it displays the post's featured image, title, excerpt, category, and date.
The post title and excerpt are rendered using the dangerouslySetInnerHTML
attribute to handle HTML content.
The component applies various CSS classes to style the post card, including hover effects and transitions.
To use the PostCard
component, import it into your desired page or component and pass a Post
object as the post
prop.
The components/posts/filter.tsx
file contains the FilterPosts
component, which provides a filtering interface for posts based on tags, categories, and authors. Here's an overview of the component:
authors
: An array of Author
objects representing the available authors to filter by.tags
: An array of Tag
objects representing the available tags to filter by.categories
: An array of Category
objects representing the available categories to filter by.selectedAuthor
: An optional string representing the currently selected author ID.selectedTag
: An optional string representing the currently selected tag ID.selectedCategory
: An optional string representing the currently selected category ID.The component uses the useRouter
hook from Next.js to handle navigation and URL updates based on the selected filters.
It renders three Select
components for filtering posts by tag, category, and author. Each Select
component displays the available options and allows the user to select a specific value or choose "All" to reset the filter.
When a filter value is changed, the handleFilterChange
function is called with the filter type and selected value. It updates the URL query parameters accordingly and navigates to the updated URL.
The component also includes a "Reset Filters" button that, when clicked, calls the handleResetFilters
function to navigate back to the /posts
page without any filters applied.
The selected filter values are passed as props to the component and used to set the initial values of the Select
components.
The template includes a powerful search system that works seamlessly with WordPress's REST API:
Located in components/posts/search-input.tsx
, the SearchInput component provides real-time search capabilities:
// Usage exampleimport { SearchInput } from "@/components/posts/search-input";<SearchInput defaultValue={search} />
Features:
The search system is implemented across several layers:
Client-Side Component (search-input.tsx
):
Server-Side Processing (page.tsx
):
WordPress API Integration (wordpress.ts
):
The following search-related functions are available in lib/wordpress.ts
:
// Search posts with combined filtersgetAllPosts({search?: string,author?: string,tag?: string,category?: string})// Search specific content typessearchCategories(query: string)searchTags(query: string)searchAuthors(query: string)
// In your page componentconst { search } = await searchParams;const posts = search? await getAllPosts({ search }): await getAllPosts();
The search functionality automatically updates filters and results as you type, providing a smooth user experience while maintaining good performance through debouncing and server-side rendering.
This starter includes automatic OG image generation for both posts and pages. The OG images are generated on-demand using the Edge Runtime and include:
You can test the OG image generation by visiting:
/api/og?title=Your Title&description=Your Description
The OG images are automatically generated for:
/posts/[slug]
/pages/[slug]
Each OG image includes:
The sitemap for next-wp
is generated at @/app/sitemap.ts
and will appear live on your site at yourdomain.com/sitemap.xml
. In order to set up your sitemap correctly please make sure to update the site_domain
in the site.config.ts
to be the domain of your frontend (not your WordPress instance).
This starter implements an intelligent caching and revalidation system using Next.js 15's cache tags. Here's how it works:
The WordPress API functions use a hierarchical cache tag system:
wordpress
(affects all content)posts
, pages
, categories
, etc.post-123
, category-456
, etc.Install the WordPress Plugin:
wordpress/next-revalidate/
Configure Next.js:
WORDPRESS_WEBHOOK_SECRET
to your environment variables/api/revalidate
is already set upHow it Works:
You can also manually revalidate content using the revalidateWordPressData
function:
// Revalidate all WordPress contentawait revalidateWordPressData();// Revalidate specific content typesawait revalidateWordPressData(["posts"]);await revalidateWordPressData(["categories"]);// Revalidate specific itemsawait revalidateWordPressData(["post-123"]);await revalidateWordPressData(["category-456"]);
This system ensures your content stays fresh while maintaining optimal performance through intelligent caching.
Built by Bridger Tower and Cameron Youngblood at 9d8
next-wp is a Next.js application for Headless WordPress. Includes functions for fetching posts, categories, tags, pages, and featured media.
This is a starter template for building a Next.js application that fetches data from a WordPress site using the WordPress REST API. The template includes functions for fetching posts, categories, tags, authors, and featured media from a WordPress site and rendering them in a Next.js application.
next-wp
is built with Next.js 15, React, Typescript, Tailwind, shadcn/ui, and brijr/craft. It pairs nicely with brijr/components for a rapid development experience. Built by Cameron and Bridger at 9d8.
lib/wordpress.ts
-> Functions for fetching WordPress CMS via Rest API with cache tagslib/wordpress.d.ts
-> Type declarations for WP Rest APIcomponents/posts/post-card.tsx
-> Component and styling for postsapp/posts/filter.tsx
-> Component for handling filtering of posts/menu.config.ts
-> Site nav menu configuration for desktop and mobile/site.config.ts
-> Configuration for sitemap.ts
app/sitemap.ts
-> Dynamically generated sitemapapp/api/revalidate/route.ts
-> Webhook endpoint for content revalidationwordpress/next-revalidate/
-> WordPress plugin for automatic revalidationThe following environment variables are required in your .env.local
file:
WORDPRESS_URL="https://wordpress.com"WORDPRESS_HOSTNAME="wordpress.com"WORDPRESS_WEBHOOK_SECRET="your-secret-key-here"
You can find the example of .env.local
file in the .env.example
file (and in Vercel).
The lib/wordpress.ts
file contains a comprehensive set of functions for interacting with the WordPress REST API. Each function is optimized for Next.js 15's caching system and includes proper error handling.
// Default fetch options for all WordPress API callsconst defaultFetchOptions = {next: {tags: ["wordpress"],revalidate: 3600, // 1 hour cache},headers: {Accept: "application/json","Content-Type": "application/json",},};
getAllPosts(filterParams?: { author?: string; tag?: string; category?: string; })
: Fetches posts with optional filtering by author, tag, or category. Uses cache tags for efficient revalidation.
getPostById(id: number)
: Retrieves a specific post by ID with proper error handling.
getPostBySlug(slug: string)
: Fetches a post using its URL-friendly slug.
getAllCategories()
: Retrieves all categories with cache invalidation support.
getCategoryById(id: number)
: Gets a specific category with error handling.
getCategoryBySlug(slug: string)
: Fetches a category by its slug.
getPostsByCategory(categoryId: number)
: Gets all posts in a category, using proper cache tags.
getAllTags()
: Fetches all available tags.
getTagById(id: number)
: Retrieves a specific tag.
getTagBySlug(slug: string)
: Gets a tag by its slug.
getTagsByPost(postId: number)
: Fetches all tags associated with a post.
getPostsByTag(tagId: number)
: Gets all posts with a specific tag.
getAllPages()
: Retrieves all WordPress pages.
getPageById(id: number)
: Gets a specific page by ID.
getPageBySlug(slug: string)
: Fetches a page by its slug.
getAllAuthors()
: Fetches all WordPress authors.
getAuthorById(id: number)
: Gets a specific author.
getAuthorBySlug(slug: string)
: Retrieves an author by slug.
getPostsByAuthor(authorId: number)
: Gets all posts by a specific author.
getFeaturedMediaById(id: number)
: Retrieves featured media (images) with size information.All functions use the custom WordPressAPIError
class for consistent error handling:
class WordPressAPIError extends Error {constructor(message: string, public status: number, public endpoint: string) {super(message);this.name = "WordPressAPIError";}}
Each function supports Next.js 15's cache tags for efficient revalidation:
// Example cache configuration{next: {tags: ["wordpress", "posts", `post-${id}`],revalidate: 3600,}}
try {// Fetch posts with filteringconst posts = await getAllPosts({author: "123",category: "news",tag: "featured",});// Handle errors properly} catch (error) {if (error instanceof WordPressAPIError) {console.error(`API Error: ${error.message} (${error.status})`);}}
These functions are designed to work seamlessly with Next.js 15's App Router and provide proper TypeScript support through the types defined in wordpress.d.ts
.
The lib/wordpress.d.ts
file contains comprehensive TypeScript type definitions for WordPress entities. The type system is built around a core WPEntity
interface that provides common properties for WordPress content:
interface WPEntity {id: number;date: string;date_gmt: string;modified: string;modified_gmt: string;slug: string;status: "publish" | "future" | "draft" | "pending" | "private";link: string;guid: {rendered: string;};}
Key type definitions include:
Post
: Blog posts and articles (extends WPEntity
)Page
: Static pages (extends WPEntity
)Author
: User informationCategory
: Post categories (extends Taxonomy
)Tag
: Post tags (extends Taxonomy
)FeaturedMedia
: Media attachments (extends WPEntity
)RenderedContent
: For content with HTML renderingRenderedTitle
: For titles with HTML renderingTaxonomy
: Base interface for categories and tags
interface FilterBarProps {authors: Author[];tags: Tag[];categories: Category[];selectedAuthor?: Author["id"];selectedTag?: Tag["id"];selectedCategory?: Category["id"];onAuthorChange?: (authorId: Author["id"] | undefined) => void;onTagChange?: (tagId: Tag["id"] | undefined) => void;onCategoryChange?: (categoryId: Category["id"] | undefined) => void;}
interface MediaDetails {width: number;height: number;file: string;sizes: Record<string, MediaSize>;}interface MediaSize {file: string;width: number;height: number;mime_type: string;source_url: string;}
All types are designed to be:
The components/posts/post-card.tsx
file contains the PostCard
component, which is responsible for rendering a single post card in the application. Here's an overview of the component:
post
: A Post
object representing the WordPress post to be rendered.The component fetches the featured media, author, and category associated with the post using the getFeaturedMediaById
, getAuthorById
, and getCategoryById
functions from lib/wordpress.ts
.
It formats the post date using the toLocaleDateString
method with the specified options.
The component renders a link to the individual post page using the post's slug.
Inside the link, it displays the post's featured image, title, excerpt, category, and date.
The post title and excerpt are rendered using the dangerouslySetInnerHTML
attribute to handle HTML content.
The component applies various CSS classes to style the post card, including hover effects and transitions.
To use the PostCard
component, import it into your desired page or component and pass a Post
object as the post
prop.
The components/posts/filter.tsx
file contains the FilterPosts
component, which provides a filtering interface for posts based on tags, categories, and authors. Here's an overview of the component:
authors
: An array of Author
objects representing the available authors to filter by.tags
: An array of Tag
objects representing the available tags to filter by.categories
: An array of Category
objects representing the available categories to filter by.selectedAuthor
: An optional string representing the currently selected author ID.selectedTag
: An optional string representing the currently selected tag ID.selectedCategory
: An optional string representing the currently selected category ID.The component uses the useRouter
hook from Next.js to handle navigation and URL updates based on the selected filters.
It renders three Select
components for filtering posts by tag, category, and author. Each Select
component displays the available options and allows the user to select a specific value or choose "All" to reset the filter.
When a filter value is changed, the handleFilterChange
function is called with the filter type and selected value. It updates the URL query parameters accordingly and navigates to the updated URL.
The component also includes a "Reset Filters" button that, when clicked, calls the handleResetFilters
function to navigate back to the /posts
page without any filters applied.
The selected filter values are passed as props to the component and used to set the initial values of the Select
components.
The template includes a powerful search system that works seamlessly with WordPress's REST API:
Located in components/posts/search-input.tsx
, the SearchInput component provides real-time search capabilities:
// Usage exampleimport { SearchInput } from "@/components/posts/search-input";<SearchInput defaultValue={search} />
Features:
The search system is implemented across several layers:
Client-Side Component (search-input.tsx
):
Server-Side Processing (page.tsx
):
WordPress API Integration (wordpress.ts
):
The following search-related functions are available in lib/wordpress.ts
:
// Search posts with combined filtersgetAllPosts({search?: string,author?: string,tag?: string,category?: string})// Search specific content typessearchCategories(query: string)searchTags(query: string)searchAuthors(query: string)
// In your page componentconst { search } = await searchParams;const posts = search? await getAllPosts({ search }): await getAllPosts();
The search functionality automatically updates filters and results as you type, providing a smooth user experience while maintaining good performance through debouncing and server-side rendering.
This starter includes automatic OG image generation for both posts and pages. The OG images are generated on-demand using the Edge Runtime and include:
You can test the OG image generation by visiting:
/api/og?title=Your Title&description=Your Description
The OG images are automatically generated for:
/posts/[slug]
/pages/[slug]
Each OG image includes:
The sitemap for next-wp
is generated at @/app/sitemap.ts
and will appear live on your site at yourdomain.com/sitemap.xml
. In order to set up your sitemap correctly please make sure to update the site_domain
in the site.config.ts
to be the domain of your frontend (not your WordPress instance).
This starter implements an intelligent caching and revalidation system using Next.js 15's cache tags. Here's how it works:
The WordPress API functions use a hierarchical cache tag system:
wordpress
(affects all content)posts
, pages
, categories
, etc.post-123
, category-456
, etc.Install the WordPress Plugin:
wordpress/next-revalidate/
Configure Next.js:
WORDPRESS_WEBHOOK_SECRET
to your environment variables/api/revalidate
is already set upHow it Works:
You can also manually revalidate content using the revalidateWordPressData
function:
// Revalidate all WordPress contentawait revalidateWordPressData();// Revalidate specific content typesawait revalidateWordPressData(["posts"]);await revalidateWordPressData(["categories"]);// Revalidate specific itemsawait revalidateWordPressData(["post-123"]);await revalidateWordPressData(["category-456"]);
This system ensures your content stays fresh while maintaining optimal performance through intelligent caching.
Built by Bridger Tower and Cameron Youngblood at 9d8