or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

docs

core-react.mdcss-styling.mddom-integration.mderror-boundaries.mdevent-system.mdindex.mdreact-hooks.mdrouting.mdserver-side-rendering.mdtesting-utilities.md
tile.json

server-side-rendering.mddocs/

Server-Side Rendering

Server-side rendering utilities for generating HTML strings from React elements, supporting both standard and static markup generation.

Core Functions

[@bs.module "react-dom/server"]
external renderToString: React.element => string = "renderToString";

[@bs.module "react-dom/server"]
external renderToStaticMarkup: React.element => string = "renderToStaticMarkup";

Usage Examples

Basic SSR

[@react.component]
let make = (~title: string, ~content: string) => {
  <html>
    <head>
      <title> {React.string(title)} </title>
      <meta charset="utf-8" />
    </head>
    <body>
      <div id="root">
        <h1> {React.string(title)} </h1>
        <p> {React.string(content)} </p>
      </div>
    </body>
  </html>
};

/* Server-side rendering */
let renderPage = (title, content) => {
  let html = ReactDOMServer.renderToString(<App title content />);
  "<!DOCTYPE html>" ++ html;
};

/* Usage */
let htmlString = renderPage("My Page", "Welcome to my site!");

Static Site Generation

[@react.component] 
let make = (~posts: array(post)) => {
  <html>
    <head>
      <title> {React.string("Blog")} </title>
      <meta charset="utf-8" />
    </head>
    <body>
      <main>
        <h1> {React.string("Latest Posts")} </h1>
        <ul>
          {posts->Array.map(post =>
            <li key=post.id>
              <a href={"/posts/" ++ post.slug}>
                {React.string(post.title)}
              </a>
            </li>
          )->React.array}
        </ul>
      </main>
    </body>
  </html>
};

/* Generate static HTML (no React hydration data) */
let generateStaticBlog = (posts) => {
  let html = ReactDOMServer.renderToStaticMarkup(<BlogPage posts />);
  "<!DOCTYPE html>" ++ html;
};

SSR with Hydration

[@react.component]
let make = (~initialData: Js.Json.t) => {
  <html>
    <head>
      <title> {React.string("Interactive App")} </title>
      <meta charset="utf-8" />
    </head>
    <body>
      <div id="root">
        <InteractiveComponent initialData />
      </div>
      <script 
        dangerouslySetInnerHTML={
          "__html": "window.__INITIAL_DATA__ = " ++ Js.Json.stringify(initialData)
        }
      />
      <script src="/bundle.js" />
    </body>
  </html>
};

/* Server-side render with hydration support */
let renderWithHydration = (initialData) => {
  let html = ReactDOMServer.renderToString(<HydratedApp initialData />);
  "<!DOCTYPE html>" ++ html;
};

/* Client-side hydration */
let hydrateClient = () => {
  let initialData = [%raw "window.__INITIAL_DATA__"];
  let container = ReactDOM.querySelector("#root");
  
  switch (container) {
  | Some(element) => 
    ReactDOM.hydrate(<InteractiveComponent initialData />, element)
  | None => ()
  };
};

SSR with Routing

[@react.component]
let make = (~url: ReasonReactRouter.url) => {
  let content = switch (url.path) {
  | [] => <HomePage />
  | ["about"] => <AboutPage />
  | ["posts", slug] => <PostPage slug />
  | _ => <NotFoundPage />
  };
  
  <html>
    <head>
      <title> {React.string("My Site")} </title>
      <meta charset="utf-8" />
    </head>
    <body>
      <div id="root">
        <nav>
          <a href="/"> {React.string("Home")} </a>
          <a href="/about"> {React.string("About")} </a>
        </nav>
        <main> {content} </main>
      </div>
      <script src="/bundle.js" />
    </body>
  </html>
};

/* Server route handler */
let handleRoute = (requestPath: string) => {
  let url = ReasonReactRouter.dangerouslyGetInitialUrl(
    ~serverUrlString=requestPath, 
    ()
  );
  
  let html = ReactDOMServer.renderToString(<RoutedApp url />);
  "<!DOCTYPE html>" ++ html;
};

Error Handling in SSR

let safeRenderToString = (element: React.element) => {
  try {
    ReactDOMServer.renderToString(element);
  } catch {
  | Js.Exn.Error(e) => 
    Js.log2("SSR Error:", e);
    ReactDOMServer.renderToString(
      <html>
        <body>
          <h1> {React.string("Server Error")} </h1>
          <p> {React.string("Please try again later.")} </p>
        </body>
      </html>
    );
  | _ => 
    ReactDOMServer.renderToString(
      <html>
        <body>
          <h1> {React.string("Unknown Error")} </h1>
        </body>
      </html>
    );
  }
};

Key Differences

renderToString vs renderToStaticMarkup

  • renderToString: Includes React hydration attributes (data-reactroot, etc.) for client-side hydration
  • renderToStaticMarkup: Produces clean HTML without React attributes for static sites
let interactiveHTML = ReactDOMServer.renderToString(<App />);
/* Output: <div data-reactroot="">...</div> */

let staticHTML = ReactDOMServer.renderToStaticMarkup(<App />); 
/* Output: <div>...</div> */