HeadstartWP
repository·develop·Indexed 41 days ago
https://github.com/10up/headstartwpA modern headless WordPress solution designed to bridge WordPress with frontend frameworks like Next.js. It includes a suite of tools such as @headstartwp/core for framework-agnostic components, @headstartwp/next for Next.js bindings, @headstartwp/block-primitives, and @headstartwp/epio-search for ElasticPress.io integration. The ecosystem also provides @10up/next-redis-cache-provider as a Redis-based replacement for the Next.js Incremental Cache Handler.
What's inside HeadstartWP
- This repository is used for the distribution of the HeadstartWP plugin. All active development, issue reporting, and Pull Requests should be directed to the main monorepo at 10up/headstartwp.
Understand the HeadstartWP technology stack and principles
developHeadstartWP is built on a philosophy of simplicity and low maintenance. Key architectural decisions include:
- Foundation: The framework uses Next.js as its core full-stack React framework. This allows developers to leverage the Next.js ecosystem and Vercel's maintenance instead of managing a custom foundation.
- Complexity Reduction: The framework is designed to handle the 'wiring' between Next.js and WordPress, allowing engineers to focus on site features rather than integration plumbing.
- Data Fetching: The framework currently prioritizes the WordPress REST API over WPGraphQL. This decision is made to avoid the additional complexity of managing GraphQL-specific requirements like caching, persisted queries, and cache-bursting in most standard headless use cases.
How On-demand ISR revalidation works
developThe revalidation process follows these steps:
- WordPress saves a post: A user saves or publishes content in WordPress.
- Plugin generates token: The WordPress plugin generates a secure JWT token.
- Plugin calls Next.js: The plugin makes an HTTP request to the configured revalidate endpoint (default
/api/revalidate). - Token verification: Next.js verifies the token using
VerifyTokenFetchStrategy. - Path revalidation: Next.js calls
revalidatePath()on the verified path. - Response: A success or error response is sent back to WordPress.
Multi-site Path Construction
For multisite installations, the handler automatically adjusts path construction to include the site slug:
- Single site:
/blog/my-post - Multi-site:
/site-slug/blog/my-post - Multi-site with locale:
/en/site-slug/blog/my-post
Use `preview.usePostLinkForRedirect` for automatic routing
developThe
preview.usePostLinkForRedirectsetting inheadstartwp.config.jsallows HeadstartWP to automatically determine the correct Next.js route by using the WordPress post permalink.This is highly effective when your Next.js route structure strictly follows your WordPress permalink structure (e.g.,
/news/category/post-name). If you use complex permalinks that include categories, this setting removes the need for manualgetRedirectPathlogic in your API route.Note on Drafts: Since draft posts often lack 'pretty' permalinks in WordPress, HeadstartWP provides a REST field
_headless_wp_preview_linkwhich returns a pretty permalink even for drafts. ThepreviewRouteHandleruses this field to ensure seamless redirection.Implementation Tip: If you are overriding permalinks via a WordPress filter, ensure you provide a fallback for
post_nameso that draft posts can still generate a valid link.module.exports = { // ... other configs preview: { usePostLinkForRedirect: true, }, };Configure queryPostOrPosts priority and route matching
developBy default,
queryPostOrPostsattempts to match a single post before attempting an archive query. You can change this behavior usingparams.priorityandparams.routeMatchStrategy.Use
priority: 'archive'if you want the function to check for an archive match (like a category or tag page) before checking if the URL matches a specific post. This is useful for routes like/blog/[[...path]]where you might want the archive to take precedence over a post with a similar slug.export default async function BlogPage({ params }: HeadstartWPRoute) { const { isSingle, isArchive, data } = await queryPostOrPosts({ routeParams: await params, params: { single: { postType: 'post' }, archive: { postType: 'post', perPage: 10 }, // Try archive first for this route priority: 'archive', routeMatchStrategy: 'archive', }, }); // ... }Understand the basics of `theme.json`
developThe
theme.jsonfile allows you to control WordPress blocks in both the editor and the frontend. It is structured into two main sections:settings: Used to define global options.styles: Used to define global styles or override/adjust settings at a block level.
Values provided in
theme.jsonare automatically converted into CSS custom properties following a fixed naming scheme, which are then available on both the frontend and in the editor.How Polylang integration works
developOnce enabled, HeadstartWP automatically manages the
langattribute for all REST API calls made through the framework's data-fetching layer.Language Detection Flow:
- Route Parameter: The locale is extracted from the
[lang]dynamic segment in the URL. - Middleware:
AppMiddlewarehandles locale detection and routing. - API Calls: Data fetching functions automatically include the current locale in WordPress API requests.
- Fallback: If no locale is specified in the URL, the
defaultLocalefromheadstartwp.config.jsis used.
URL Pattern Example:
https://mysite.com/en/(English home page)https://mysite.com/pt/blog/meu-post(Portuguese blog post)
- Route Parameter: The locale is extracted from the
Understand the queryPosts return value
developThe
queryPostsfunction returns a Promise that resolves to an object containingdataandconfig.data object
posts: An array ofPostEntityobjects.queriedObject: (Optional) When fetching by category, tag, or author, this contains information about the queried term or author.pageInfo: An object containing pagination metadata:page: Current page number.totalPages: Total number of pages available.totalItems: Total number of items matching the query.hasNextPage: Boolean indicating if a next page exists.hasPreviousPage: Boolean indicating if a previous page exists.
Understand block attribute serialization in HTML
developWhen HeadstartWP processes a block, it serializes the Gutenberg attributes into the HTML markup. This is what enables
forwardBlockAttributesto work. The renderer looks for thedata-wp-blockattribute, which contains a JSON string of the block's attributes.Example HTML Output:
<div class="wp-block-custom-hero" data-wp-block-name="custom/hero" data-wp-block='{"title":"Welcome to Our Site","content":"This is the hero section","backgroundImage":{"url":"https://example.com/hero-bg.jpg","alt":"Hero background"},"buttonText":"Learn More","buttonUrl":"/about"}' > <!-- Block content --> </div>When your React component is used within a
BlocksRendererwithforwardBlockAttributes={true}, these JSON values are automatically parsed and passed into your component'sblock.attributesprop.How Feeds work in HeadstartWP
developBy default, Feeds are proxied via Next.js rewrites. This allows you to access the feed directly through your front-end/Next.js URL.
If the HeadstartWP plugin is enabled, the URLs contained within the feed XML will also be rewritten to use your front-end URLs instead of the WordPress backend URLs. This rewriting behavior is triggered by the presence of the
?rewrite_urls=1query parameter. The framework automatically appends this parameter when accessing feeds through the Next.js proxy.{ source: `${prefix}/feed`, destination: `${wpUrl}/feed/?rewrite_urls=1`, }Data fetching in Next.js App Router
developHeadstartWP uses modern async/await patterns for data fetching in the Next.js App Router. Instead of relying on React hooks (which are used in the Pages Router), the App Router leverages Server Components for efficient server-side fetching. The underlying logic is abstracted via
AbstractFetchStrategyclasses.Key features of the App Router data-fetching functions include:
- Automatic extraction of URL segments from route parameters.
- Support for WordPress pretty permalinks.
- Full TypeScript support.
- Integration with Next.js caching and revalidation mechanisms.
How block styles are processed and delivered
developWhen block styles are enabled, HeadstartWP follows this lifecycle to optimize performance:
- Collects Global Styles: Includes the WordPress global stylesheet via
wp_get_global_stylesheet(). - Processes Core Block Supports: Adds CSS for core block features and supports.
- Loads Individual Block Styles: Gathers CSS for each block type present on the page using their specific style handles.
- Combines and Delivers: Combines all gathered CSS into a single
block_stylesproperty within the REST API response.
The resulting CSS is available in your headless frontend via
post.content.block_styles, allowing you to render it inline or process it within React components. This ensures only the CSS for blocks actually used on a page is loaded.- Collects Global Styles: Includes the WordPress global stylesheet via