Sitecore Next.js App Router: Part 2, Server Components

Anton Tishchenko
Anton Tishchenko
Cover Image for Sitecore Next.js App Router: Part 2, Server Components

We reviewed the usage of Sitecore of Next.js App Router together with client components in our previous article. Client components were the limitation for features that you would like to have with App Router: Server Components and Streaming.

Today, we will look at what could be done to force Sitecore to work with server components. We will cut corners to get an understanding, of what should be done to make Sitecore Next.js App Router work with server components quicker. But, everything that is described in this blog post was tried, and it works as expected.

SitecoreContext

SitecoreContext is a crucial part of the Sitecore Next.js website. It provides information about the current page. Also, it is the way how to access the components factory. Placeholders will not work without the components factory. You will not be able to render the Sitecore page. If you look at the SitecoreContext implementation, you will see that it contains two parts: ComponentFactoryReactContext.Provider and SitecoreContextReactContext.Provider

Components Factory

We can simplify things for our PoC. A components factory is just a function. The function that receives the name of the component and returns another function that renders the required component. Yes, it will be nice to save it in some context and access it. But the simplest way is to import the component factory as a function the the file directly. It will be enough for us in the first stage.

Page Context

Page information is the second important thing that is saved in the SitecoreContext. layoutData is a synonym for it. We can’t use createContext and useContext hooks as it was done for Page Router. These hooks don’t work together with server components. There are plenty of other ways, how we can replace it. It can be some state manager that doesn’t rely on the browser API, e.g. nanostores or zustand. We can use one of the many different cache implementations: React cache, “use cache” Next.js directive, unstable_cache from Next.js, or just call layout service multiple times and rely on Next.js built-in fetch caching. All these approaches will work. And it is hard to select the best one. Adding state managers will make JSS opinionated about state manager, which I would like to avoid. This decision should be made by the team that makes implementation of the website itself. Caching looks like the subject that is going to be changed in the future Next.js version and I don’t want to spend time on it now in the PoC if it is going to be changed.

And we have another option. State management for poor: global variable. It doesn’t look like a proper decision, it sounds wrong, but it is enough for us. We need to write this variable once at the top-level component. And we can access this variable multiple times inside our components.

I will use a global variable for my PoC and will leave task for the selection of the proper approach for future me or future Sitecore JSS developers.

Translations

Next.js has built-in support for internationalization. I don’t know the reason, but the Sitecore team decided to use next-intl library. Let’s stick to this decision. next-intl has proper support for App Router without any limitations.

Placeholder

Placeholder is a crucial part of page composition. It is a class component in the current JSS implementation. It does many things. The main function of Placeholder is taking the proper component using the components factory and rendering it. We will implement only this one, main function. We will skip renderEach for complex layouts, renderEmpty for rendering empty placeholder in the editing mode, we will skip rendering chromes tags for editing mode.

It will be a simple function server-side component that takes layout data and placeholder key as props and renders the required component. It will be enough for PoC.

Sitecore JSS field implementations

There are some dependencies on the browser API inside JSS fields. For example, RichText Sitecore JSS field relies on useRouter hook, which is also available only for client components. It is used to prefetch links for quicker navigation. You can replace it with next/link, but you will lose prefetch functionality. Indeed, all fields can be client components. You use them inside your code, they are not using your component like it is with Placeholder. Leaving use client is not a big deal here, it will not stop us from using server components and streaming.

Other

Many more places force us to add use client directives. But they are not so important to make a first run. We can avoid using this functionality. We can have a website without high-order functions like withPlaceholder, withSitecoreContext, withEditorChromes, withComponentFactory, withDatasourceCheck. I can bet that you don’t even know, what HoF withEditorChromes does without looking into documentation. We will not have all the features, but we will have enough features to make the first test and understand the benefits of Next.js App Router for Sitecore.

Conclusion

I was able the Sitecore JSS Next.js App Router website run on server components. If you don’t want to wait for official support from Sitecore from App Router, you can start to use it now! You can leverage the power of server components and streaming!

You can use this article and my fork as a recipe. It allows you to understand the level of effort and what should be changed. I understand that proper support of the Next.js App Router will require a lot of additional work and a lot of changes. Breaking changes! It will require months and a proper skill set to do it. My prediction is that there will be a separate version of JSS for Pages Router and a separate version for App Router.

Do you want to get the performance comparison of Sitecore websites for Pages Router, App Router with client components, and App Routers on server components? Stay tuned, it will be in the next post!