React Server Components and Client Components with Rollup
With the release of Next 13 and introduction of the beta
app/ directory, we now have support for React Server Components, and can start building applications using these new React paradigms.
A starter template for React using the setup covered in that post is available here:
Now, that template is great. I've used it in several libraries that have been published to production for several years now without problems.
However, when trying to import a component from a library built using that starter template in a Next 13
app directory, you get the following error:
Server Component import error
Next 13 pages inside
app directory are React Server Components by default. In fact, anything deemed a React component, when consumed within the
app directory, is considered to be a Server Component by default.
To make a React component into a Client Component (ie. our regular old React components with effects and state that we all use and love), you need to denote it with a
'use client' directive at the top of the component file:
Client Components are any components that rely on client-side only features, such as:
So, when I tried to import a
Button component (which has event handlers and is "interactive") inside a Server Component, React had no idea that it was supposed to be a Client Component. It didn't see a
'use client' anywhere in the library code, so it assumed that it's a Server Component.
This is something that component libraries will need to start addressing soon, as Server Components start to gain ground (and some already have). I wanted to make sure that my starter template supported Server Components properly.
One way to address this would be to mark all components in our library as Client Components. This would mean having a top-level
'use client' directive in each file (or at the top of the bundled file if not using directories).
To do this, include a
banner option in your
output options inside the
rollup.config.js, and add
'use client' at the very end of it:
Now, if you leave it as is, the
terser plugin will remove it, so we need to tell it to preserve directives by setting the
compress.directives option to
And now if you run the
build script, you'll see that our bundled files have that included at the top.
...or is it? 🤔
While the above solution works, it's not very elegant.
We don't necessarily want every component to be a Client Component. There can be components in your library that don't rely on state or use any effects, and can very well benefit from being Server Components.
Well, we've already configured Rollup to output separate files for each of our modules. What if we simply add
'use client' to the component files themselves?
However... If we try to run the
build, we'll see a warning like this:
Rollup build warning
Rollup will simply ignore this directory and not include it in our final bundle. And this is a good thing, in general. However, in this case we want this directive to be included. This is where Rollup community comes to the rescue 🎉
While searching for a solution, I came across this issue. Ironically, one of the suggested solutions in there was the initial solution described earlier above. However, there was also another suggestion.
Fredrik Höglund shared his plugin that solves our exact problem:
This plugin, as its name implies, preserves any directives written at the top of our files:
This plugin preserves directives when
preserveModules: trueis set in the Rollup config.
Rollup by default always removes directives like
'use client'from the top of files. This makes sense when bundling files because directives should be applied per file, which is not possible when bundling.
preserveModules: trueis set, because each module is a separate output file, it's possible to keep directives, which is exactly what this plugin does.
Because we're already using the
preserveModules option in our Rollup config, this will work great.
With this plugin, we can safely leave the
'use client' directives in our component files (where they belong), and Rollup won't remove them from our bundled files.
To use the plugin, first install it:
Then, add it to the
plugins array in your
We need to use
preserveDirectives.default()instead of just
preserveDirectives()here, due to an issue with the plugin in ESM environments.
build again, we can see that the
'use client' directive is preserved in the output files:
Preserved "use client" directive
And now we can import it in a Server Component and use without any issues:
Disabling the warning
rollup-preserve-directives plugin doesn't suppress that Rollup warning we saw earlier. Its README suggests to add a custom
onwarn handler to the Rollup config if desired.
Let's do that. Add the following to
This will suppress any
MODULE_LEVEL_DIRECTIVE warnings, and throw all others. See Rollup docs for more details.
rollup-library-starter template has been updated to use this plugin, and is available in GitHub for your reference: