Skip to content

Server rendering

Using Youj with React Server Components and server rendering: what runs where, what the server and the first browser render agree on, and what changes after hydration.

Youj renders on the server. The packages mark their client components with "use client", so a Server Component imports them directly, with nothing to transpile, and the page arrives as HTML.

What runs where

  • Import Youj components from a Server Component. import { Card } from '@youj/react' works in a file with no directive.
  • Functions come from a Client Component. A prop that's a function (onAction, onValueChange, render) can't cross from a Server Component. Put the component that passes it in a file that starts with 'use client'.
  • A sound pack is such a function. sound={beacon} goes on a YoujProvider in a client file; npx @youj/cli add sound writes app/providers.tsx for this on the App Router.
  • Hooks are client-side. useYouj, useDevice and the rest read the provider, so use them in a Client Component.
  • What reads the clock goes inside Suspense under cacheComponents. create-next-app turns Next.js's cacheComponents on, and then next build stops when a Client Component reads the current time while its page prerenders. The Date Range Picker does (its presets count from today), and so do blocks and examples that count from now (a countdown, an upload's progress, a schedule's current hour). Wrap them, or the part of the page around them, in <Suspense> and they render per request. npx @youj/cli create wraps its screens for you.

The theme, without a flash

Spread the theme onto <html> in your root layout:

import { basalt, themeProps } from '@youj/core'

<html lang="en" suppressHydrationWarning {...themeProps(basalt)}>

themeProps returns the attributes that apply a theme to an element: its contract as CSS variables, the preset's id and its motion personality. The server's HTML already carries the theme, so the first paint is themed.

The light or dark scheme is set on <html> by a script before React hydrates, which is why <html> needs suppressHydrationWarning. npx @youj/cli init writes both in a Next.js or React Router app. See Installation for the setup by hand.

What the first render assumes

The server and the browser's first render must agree, so some things start from a fixed value and correct themselves right after hydration:

WhatOn the server and the first renderAfter mount
Device (useDevice)pointertouch if the primary pointer is coarse
Reduced motionThe OS setting is off, unless you set reducedMotion="always" on the providerFollows the OS setting
DirectionThe provider's or <html>'s dir, else ltrauto is read from the element
Quality (quality="auto")The default tierMeasured on the device
Numbers, datesThe provider's locale, en-US by defaultThe same
"5 min ago", local clock timesUTCThe browser's time zone

What this means for your code:

  • Style with the touch: variant, not with useDevice. CSS applies before hydration. A device read in JS is for what a component does when it opens (a sheet or a popover), not for markup that's on the page at load.
  • Set what you know on the server. Pass the user's locale and dir to YoujProvider from the request, and reducedMotion="always" if you want it on from the first render.
  • Expect a relative time to change once. If your own markup shows one, render it the same way.

Motion

Motion's engine loads on demand, after the page: components render their resting state on the server and until the chunk lands, then animate. See Motion loads on demand.