Use Client Hints to Eliminate Content Layout Shift

Kent C. Dodds
Kent C. Dodds

Sometimes our components need to render something that's only known by the client. For example, what the user's preferred color scheme is or their timezone offset. So when the server wants to render, we're stuck guessing and if we guess wrong the user will wind up with a bad Content Layout Shift which is a bad user experience and reduces their confidence in our app.

The client hints feature in the Epic Stack allows you to communicate these values from the client to the server following the pattern established by the (work in progress) standard. By using cookies for storing these values, you can ensure the server will know the proper values at server render time so you can take the user preferences into account.

Even if you're not using the Epic Stack, Remix, or even React, you can apply this same setup to your own Web app.


Instructor: 0:00 The Epic Stack just added support for client preference cookies. What is this? Basically, it's an implementation of a new standard that's coming and being worked on for the server to be able to tell the client it needs some additional information before it can render.

0:18 If you want to make a really awesome user experience, you want to server-render something, and then when the client takes over and adds event listeners and stuff, it doesn't actually need to change anything. If it does, then it's going to result in a really bad content layout shift and it makes for a really poor experience.

0:35 Examples of this are a flash of the incorrect theme or a time zone problem where it renders some time on the server and then on the client is in a different time zone so it renders a different time. Things like that can be a real problem. The browser can't just send all of the user's information to the server because there could be a lot of things that the server doesn't care about.

0:59 The way that this standard works is the browser will say, "Hey, I want to get this resource," and then the server will say, "Oh, before you can get that resource, I need a couple extra information." It sends a response back. The browser says, "OK, cool, I'll send you that information," and then the server can send the correct response with the details that now the server needs.

1:21 This works out really well. We've implemented an inspired feature. It's not exactly the same sort of thing. In fact, when the standard is finished, we'll probably keep our implementation because it does have some benefits on top of that. Just a couple improvements, and so we'll probably keep our thing around. It is definitely inspired by this sort of thing.

1:46 Let's take a look at an example. Let's say that I'm building something for the Epic Stack and I want to have this cool fade-in transition. This is actually something that I do on my website and I'm using a tool called Framer Motion that allows me to build some really awesome animations that would be extremely difficult to do if I were just using CSS.

2:08 There are some situations where using regular CSS is just not going to quite do it for you. I need to do this in JavaScript. The nice thing is that there's a useReducedMotion hook with Framer Motion. We can take that reducedMotion and determine whether we want to have the motion props on here at all.

2:33 If I have prefers-reduced-motion reduce, if I've configured that in my browser, then I navigate over here and it doesn't do that motion. If I turn that off, then it does. We can respect the user's preferences.

2:51 The problem is that this is only known at the client render time. If I turn this off and then do a refresh, then the server thinks that it's going to animate this in, so it renders it as invisible. The client doesn't animate it because the user says, "I want to prefer reduced motion." This is a bit of a problem. This is what the client hints is supposed to solve. Let's solve this using client hints.

3:19 We've already implemented the theme using the same sort of thing. We're going to do the same thing for reducedMotion. The cookie name, we're using cookies to manage these values, we'll just call it prefers-reduced-motion. Then here, the value, we're going to say no-preference.

3:42 If that matches, then we know that we do not want to reduce the motion. If it has no preference, we don't want to reduce the motion. If they do have a preference, then it will be reduced. We do want to reduce the motion. Our fallback here is going to be false. We'll just default to everybody having our animations.

4:03 For the transform, what this basically means is when we put this true and false value into the cookie, that's going to turn it into a string. In our case, we actually just want it to be a boolean. We're going to say if the value is equal to true, then we know that the value should be the boolean true. This transform just turns it from the string into a value that we can use.

4:27 With that now, let's go over here, and instead of using this useReducedMotion, we'll use the useHints. useHints will return an object that has reduced motion as a property on it. We'll just destructure that. Now we can come in here and I refresh. I don't get that animation because I have prefers-reduced-motion and I also get to see the actual value itself.

4:52 If I turn this off and I refresh, then I get that nice fade-in effect that I was going for. We have this really nice capability because the browser is communicating back to the server what its preferences are using cookies. We can see in here we're dark mode and this one is our prefers-reduced-motion and that's set to true currently.

5:15 That's client hints in the Epic Stack. You can, of course, use this in whatever React or Remix project that you're using. It is a pretty straightforward thing. Feel free to reference the client hints. If you're using the Epic Stack, just stick your client hint config right in here and then you'll be able to use it all over the app with however you need to do.

5:36 Whatever you're trying to do that's user-specific for something that's only known by the browser, now the server can know all of those things. I hope that helps. Have a good one.

More Tips