Extend Next.js' default Document class to ensure styles are rendered on the server

Thomas Greco
InstructorThomas Greco
Share this video with your friends

Social Share Links

Send Tweet
Published 6 years ago
Updated 5 years ago

In this lesson, we learn how to modify Next.js' custom Document to ensure that our custom components are rendered on both the client and the server. Components in this lesson are built using the emotion-js library.

Instructor: [00:01] Inside this index.js file, we see them using some custom components created with the Emotion JS library. If we look at the page, we'll see these come to life. However, if we refresh, we'll see that it loads the application without our styles being applied. This is a result of our styles not being rendered on the server.

[00:22] You don't have to be a UX specialist to realize that this isn't going to give you a fantastic user experience. Luckily for us, Next.js makes the process of fixing this extremely easy. By default, Next.js is going to use a custom document where it's going to set all the markup like the application's HTML and body tags, as well as the HTML that's being injected.

[00:46] In order to render our styles on the server, we need to override the document's custom behavior. To do this, we need to create a file titled _document.js and place this inside of our pages directory.

[01:03] The first thing we need to do here is import the document that we'll be extending, as well as the head, main, and Next script components. Following this, we'll also need to import the extractCritical function from the Emotion server library.

[01:20] Now that we have this imported, we can go ahead and extend Next custom document class. I'm going to say export default class custom doc, and it's going to extend that document that we just imported above.

[01:36] The first thing we're going to do is hook in a Next getInitialProps lifecycle hook. getInitialProps is going to return a context object including the response and request, and other properties helpful for when developing with Next.

[01:50] Inside the custom document, this context object also holds a renderPage function. This is what executes the actual rendering logic within our application. We'll use this with the extractCritical function in order to make sure that we're only injecting the necessary HTML within our app.

[02:12] Following this, we're also going to create a styles constant and set that equal to extractCritical, to which we're going to pass in the HTML that the renderPage function is giving us.

[02:26] We can then just return page as well as styles using the spread syntax. What that's going to do is make sure that the correct HTML, and IDs, and CSS that's needed for Emotion to render on the server is being mixed in to the object being created with that render page function.

[02:49] To effectively style our application, we also need to grab the props for many child components being loaded. This will allow us to cache the IDs being loaded and prevent Emotion from doing any extra work.

[03:03] To do this, we'll first call super on our props and then pull of this Next data and IDs property off of it. What we're saying is if there are IDs present, we're going to want to set an IDs property on the Next data object. In a moment, we'll see how we can effectively use these IDs to rehydrate our styles on the client.

[03:31] The last thing we need to do is return the markup for our document. As I mentioned earlier, this is where we add our HTML markup. We'll have all this React within the HTML tags, and then inside of our body tags, we're going to place the main, as well as the Next script imports, which is going to inject the proper HTML and load our scripts.

[03:57] Right now, it's pretty much what the custom document is going to look like by default. Here, we're going to add a title. I am going to call it SSR with Next.js, and then Emotion.js.

[04:14] More importantly, we're going to need to add a style tag within our head. In here, we'll use the dangerously set inner HTML prop. Here, we'll say HTML is equal to this.props.css. As a result of this, all the style that's being created with Emotion will effectively be loaded right around the server.

[04:37] It's going to format this quick. Oops, I have to return this, or else it's not going to work. Now we actually are looking good.

[04:48] Before we check this out though, we first want to make any modifications to any files within our pages directory. In this case, we just have this index.js file. Inside here, we're first going to import the hydrate function from Emotion. This is only going to need to be called on the client.

[05:09] Additionally, we'll need to pass in the IDs being created by extractCritical within our document component into this hydrate function. As we know, we're attaching those IDs to the Next data object. We can just pass in window.nextdata.ids in order to use any cached IDs.

[05:31] Now, if we go and visit our browser again and we hit refresh, we no longer see that black and white HTML as our styles are indeed being rendered on the server.

[05:43] One thing to point out is that although we used Emotion in this lesson, we could have just as easily set it up with glamor or styled components by extending the custom document just like we did in this lesson.

Felipe Vasquez
Felipe Vasquez
~ 5 years ago

Nowadays this is not needed. Follow the example here: https://github.com/zeit/next.js/tree/canary/examples/with-emotion

Basically using babel-plugin-emotion you are able to avoid any modification to _document.js. Just create a .babelrc file at the root of your file and write:

{
  "presets": [
    "next/babel"
  ],
  "plugins": [
    ["emotion"]
  ]
}

That's it, with this you'll achieve SSR for your styes.

Thomas Greco
Thomas Grecoinstructor
~ 5 years ago

Hey Felipe, thanks for pointing out! This setup isn't necessary anymore however it was at the time that this lesson was created.

That said, I'm pretty sure that it's no longer necessary to modify the .babelrc file. You might receive some advantages from using the babel-plugin however documentation on SSR state that it should work out of the box.

Markdown supported.
Become a member to join the discussionEnroll Today