Build a headless website fast with WordPress, the world’s most popular CMS, and Next.js, the most popular React framework. A free and open source solution by the experts at 10up.
Support both Pages Router and App Router so being able to reuse code between the two is important.
Default to "server-first" approach but still allow the client-side useSwr-powered hooks to be useable inside Client Components
Implementation Brief
The sections below describes the technical plan at a high-level.
Data Fetching
Create a version of useFetch that isn't a hook but an async function: fetchStrategy()
Create a fetchPosts, fetchPosts, fetchSearch, fetchSearchNative that uses the fetchStrategy function that executes the strategy.
The fetch* functions should accept the params from a pre-defined catch-all argument (e.g [...path].ts) so that it automatically extract params from the URL. However, this will need to be passed explicitly instead of automagically as in the pages router.
Since all of this is just a regular function, it can stay in the @headstartwp/core export.
Additionally, these methods could easily be reused with other frameworks such as Astro if we ever attempt to support other frameworks that aren't Next.js
By keeping the current Fetch Strategies as is we can reuse all of the logic between Pages Router, App Router and Client-side data-fetching.
We can look for opportunities to streamline fetch strategies. However, I don't foresee a need for utilizing the "fetch strategy way" of doing things for custom/third-party endpoints. In the Pages Router the fetch strategy was useful for allowing "isomorphic data-fetching", with the app router most of data fetching will be server side so there's no need for that. Therefore we should treat "fetch strategies" as an internal concept.
SEO Handling
With the App Router we mostly just need a utility function to extract the yoastSeo metadata out of the API.
BlocksRenderer
This is essentially completed in the PoC. The main thing to note is that it will no longer automatically load the config from the App's Context Provider since that is not avaliable in Server Components, instead, the config should be passed to it if necessary.
The example below show what will be possible using the BlocksRender and Server Components. Any custom block can fetch additional data right in the component, for instance, it can fetch a list of posts from the blocks attribute ids.
With Suspense & Streaming we can make it so that it doesn't block rendering the rest of the page and with Partial Pre-rendering we can still ship a static shell of the page while the rest of the blocks finish loading.
const MyCustomBlockSkeleton = ({domNode}) => {
return (
<Suspense fallback={<BlockSkeleton />}>
<MyCustomBlock domNode={domNode} />
</Suspense>
);
}
const MyCustomBlock = async ({domNode}) => {
const attributes = getBlockAttributes(domNode);
// no need to inject the whole list of post data into the markup
const posts = await fetchPosts({ include: attributes.ids });
return /* render posts*/;
}
const RenderBlocks = () => {
return (
<BlocksRenderer html={post.content.rendered}>
<MyCustomBlockSkeleton test={(node) => isBlockByName(node) } />
</BlocksRenderer>
);
};
We currently have a few hooks that gets the attribute we should probably convert those into regular functions since they don't need to be hooks anyway.
Additionally we could look into having BlocksRenderer automatically pass attributes as props to avoid needing block components to read the attributes themselves from domNode.
Need to look into the better way to load the config with the App Router. We could keep injecitng it at build time or we could simply provide an async function that would support loading the config in server components.
This is important because of how we support multisite today.
Multisite
Multisite will work mostly the same way.
Next.js Handlers
We'd want to move the core logic of revalidateHandler and previewHandle to a separate function to make it able to work with the Next.js specific Request/Response objects of the Pages rotuer and the Web-compatible Request/Response objects of the App Router.
The previewHandle will need some rework though as the way draft mode works is a bit different and doesn't support previewData object which we currently need.
Summary
This is the main issue for implementing App Router Support and it will describe the current plan.
Goals
Implementation Brief
The sections below describes the technical plan at a high-level.
Data Fetching
useFetch
that isn't a hook but an async function:fetchStrategy()
fetchPosts
,fetchPosts
,fetchSearch
,fetchSearchNative
that uses thefetchStrategy
function that executes the strategy.fetch*
functions should accept the params from a pre-defined catch-all argument (e.g [...path].ts) so that it automatically extract params from the URL. However, this will need to be passed explicitly instead of automagically as in the pages router.@headstartwp/core
export.By keeping the current Fetch Strategies as is we can reuse all of the logic between Pages Router, App Router and Client-side data-fetching.
We can look for opportunities to streamline fetch strategies. However, I don't foresee a need for utilizing the "fetch strategy way" of doing things for custom/third-party endpoints. In the Pages Router the fetch strategy was useful for allowing "isomorphic data-fetching", with the app router most of data fetching will be server side so there's no need for that. Therefore we should treat "fetch strategies" as an internal concept.
SEO Handling
With the App Router we mostly just need a utility function to extract the yoastSeo metadata out of the API.
BlocksRenderer
This is essentially completed in the PoC. The main thing to note is that it will no longer automatically load the config from the App's Context Provider since that is not avaliable in Server Components, instead, the config should be passed to it if necessary.
The example below show what will be possible using the BlocksRender and Server Components. Any custom block can fetch additional data right in the component, for instance, it can fetch a list of posts from the blocks attribute ids.
With Suspense & Streaming we can make it so that it doesn't block rendering the rest of the page and with Partial Pre-rendering we can still ship a static shell of the page while the rest of the blocks finish loading.
We currently have a few hooks that gets the attribute we should probably convert those into regular functions since they don't need to be hooks anyway.
Additionally we could look into having
BlocksRenderer
automatically passattributes
as props to avoid needing block components to read the attributes themselves fromdomNode
.Config Loading
Need to look into the better way to load the config with the App Router. We could keep injecitng it at build time or we could simply provide an async function that would support loading the config in server components.
This is important because of how we support multisite today.
Multisite
Multisite will work mostly the same way.
Next.js Handlers
We'd want to move the core logic of
revalidateHandler
andpreviewHandle
to a separate function to make it able to work with the Next.js specific Request/Response objects of the Pages rotuer and the Web-compatible Request/Response objects of the App Router.The
previewHandle
will need some rework though as the way draft mode works is a bit different and doesn't supportpreviewData
object which we currently need.Internationalization and Polylang Support
We'll need to add this to middleware.
https://nextjs.org/docs/app/building-your-application/routing/internationalization