skip to content

When server-rendering a React Native Web sign-up page, how do AppRegistry.getApplication and getStyleElement produce HTML with styles, and why collect styles after rendering?

level: seniorimportance: nice to knowfreq 15%

answer

  1. register once, render twice
  2. an element plus a style factory
  3. the sheet is read when you call it
  4. nonce passes straight through
  5. hydrate: true reuses the style tag

basics

~10 s

AppRegistry.getApplication returns the app element and getStyleElement. Render the element to HTML first, then call getStyleElement, which reads React Native Web's style sheet at that moment, so it contains every rule the render needed.

solid answer

~40 s

On the server you `AppRegistry.registerComponent('SignUp', () => SignUp)` and call `AppRegistry.getApplication('SignUp', { initialProps })`, which returns `{ element, getStyleElement }`. You render `element` with `react-dom/server`, then render `getStyleElement({ nonce })`, a `<style id="react-native-stylesheet">` whose text is React Native Web's style sheet **as it is when you call it**; that is why the React Native Web docs render the element first, so any rule created while rendering is included. Both go into the HTML shell, with the server's Node process aliasing `react-native` to `react-native-web`. On the client, `AppRegistry.runApplication('SignUp', { rootTag, initialProps, hydrate: true })` hydrates instead of re-rendering, and React Native Web finds the existing style element by its id and keeps inserting into it rather than creating a duplicate. The `initialProps` must match on both sides.

go deeper

for a junior

Recall that React Native Web can render on a server and that AppRegistry.getApplication gives both the app and its styles.

for a middle

Explain the element plus getStyleElement pair, the render-then-styles order, and the style element id.

for a senior

Wire the whole flow: server alias, nonce for CSP, identical initialProps, runApplication with hydrate: true, and reuse of the server style element.

for a principal

Decide whether to own a custom server-rendering stack for a few pages or adopt a framework's web output, given maintenance and SEO needs.

## The scenario The sign-up flow from the mobile codebase is served on the web, and marketing wants its first screen server-rendered so it paints fast and can be indexed. The app is not on a framework that renders it for you, so the server uses React Native Web's own server API. ## The server side React Native Web extends `AppRegistry` with **`getApplication`**: ```tsx import ReactDOMServer from 'react-dom/server'; import { AppRegistry } from 'react-native-web'; import SignUp from './src/SignUp'; AppRegistry.registerComponent('SignUp', () => SignUp); export function renderSignUp(initialProps: { referrer?: string }, nonce: string) { const { element, getStyleElement } = AppRegistry.getApplication('SignUp', { initialProps }); const html = ReactDOMServer.renderToString(element); // 1. the markup const css = ReactDOMServer.renderToStaticMarkup(getStyleElement({ nonce })); // 2. the styles return { html, css }; } ``` - **`element`** is the registered component wrapped in React Native Web's app container, with the `initialProps` applied. - **`getStyleElement(props)`** returns a `<style>` element with `id="react-native-stylesheet"` and the current style sheet text inside it; any props you pass, such as a CSP **`nonce`**, are spread onto it. - `getApplication` throws if the app key was never registered, typically because an import failed during initialisation. ## Why styles are collected after rendering `getStyleElement` does not snapshot anything when `getApplication` is called. It reads React Native Web's style sheet **at the moment you call it**: 1. Styles defined with `StyleSheet.create` at module scope are compiled into the sheet when their module is evaluated. 2. Any style compiled later, for example in a module first loaded while rendering, is only in the sheet after the render that loaded it. 3. So the React Native Web docs render the element first and the styles second. Reversing the order risks a first paint without some rules, followed by a jump when the client inserts them. On a long-running server the sheet is a module-level singleton, so it grows to the union of every style any request has needed. Because the classes are atomic, that is a bounded set, not a leak per request. ## The HTML shell The `css` goes in the `head`, the `html` in the root element: - `<head>` gets the style element (with the nonce if the page has a CSP). - `<div id="root">` gets the rendered markup. - The root element, `html` and `body` need full-height styles for a `flex: 1` app, as React Native Web's setup guide notes. - The server's Node process must resolve `react-native` to `react-native-web` too; React Native Web's docs show a module-alias package doing this for server rendering. ## The client side ```tsx import { AppRegistry } from 'react-native'; import SignUp from './src/SignUp'; AppRegistry.registerComponent('SignUp', () => SignUp); AppRegistry.runApplication('SignUp', { rootTag: document.getElementById('root'), initialProps: window.__SIGNUP_PROPS__, hydrate: true, }); ``` - **`hydrate: true`** makes `runApplication` hydrate the existing markup instead of rendering from scratch. - When the client-side style sheet is created, React Native Web **looks up the element with id `react-native-stylesheet`** and reuses it, so server rules are not duplicated and new rules land in the same element. - **`initialProps` must be identical** on both sides (serialise them into the page), otherwise the client renders something different from the server markup. ## Failure modes to recognise | Symptom | Likely cause | |---|---| | Unstyled flash before the page settles | styles collected before rendering, or the style element missing from the `head` | | Duplicate style rules in the page | the server style element lost its `react-native-stylesheet` id | | Style tag blocked by the browser | a CSP without the nonce passed to `getStyleElement` | | Client re-renders everything on load | `hydrate: true` missing from `runApplication` | | "Application has not been registered" | the app key differs between server and client, or an import failed first | ## Where this sits This is the low-level path for a custom server. An Expo Router project gets static or server output from its own web settings instead; the principle, render first and collect styles second, is the same one those tools follow.

  • In React Native Web server rendering, why pass a nonce to getStyleElement?
    Pages with a Content Security Policy that restricts inline styles only allow a `<style>` element carrying the page's nonce. `getStyleElement` spreads the props it receives onto the element, so `getStyleElement({ nonce })` emits a style tag the policy accepts.
  • What happens on the client if the server-rendered style element lost its id react-native-stylesheet?
    React Native Web would not find it when it creates its style sheet, so it would create a new `<style>` element and insert rules there. The server's rules still apply from the old element, but the page carries two copies of the styles. Keep the element exactly as `getStyleElement` produced it.

saying these in an interview costs you the question

  • getStyleElement returns the styles captured when getApplication was called.
  • Styles should be collected before rendering so nothing is missed.
  • runApplication always re-renders from scratch, even after server rendering.
  • The server can import react-native without any alias.
  • Each request's style sheet is independent, so the server resets it per render.