Creating simple static server component
Tags: servercomponents, react, webdev, javascript
Published: 2023-11-17 17:13:01

**What this article is about?**
Creating simple static server components at working project with loadable components (without next.js)

**Current setup**
The site works on a standard setup: SSR render static HTML with critical CSS at `<head>`, and has React hydration on the client to make it interactive.

Site's HTML is generated with a JSON tree, with widgets structure, coming from DB+CMS. It is a fastify app on server, with isomorphic factory. It gets JSON and includes selected components on the page. Every component has a manifest file with loadable JSON - it describe widgets names with code endpoints and import names.

**Main idea**
Write a simple client widget to directly render HTML from the server, and don't load widget chunk to client.

**Let's start**
Get one of the largest static widgets - markdown. It weight - 43kb (min+gzip)

Change the manifest by splitting the common link into the server and client.

Current setup:
```jsx
  MarkdownContainer: {
    component: loadable(
      () =>
        import(/* webpackChunkName: "MarkdownContainer" */ "./desktop"),
      {
        resolveComponent: (mod) => mod.MarkdownContainer,
      }
    ),
  },

```

Change to 

```jsx
  MarkdownContainer: {
    componentServer: loadable(
      () => import(/* webpackChunkName: "MarkdownContainer" */ "./desktop"),
      {
        resolveComponent: (mod) => mod.MarkdownContainer,
      }
    ),
    componentClient: loadable(
      () =>
        import(
          /* webpackChunkName: "MarkdownContainer" */ "~/components/fake-component"
        ),
      {
        resolveComponent: (mod) => mod.FakeTransformer,
      }
    ),
    SSRC: true,
  },
  MarkdownContainer_css: {
    component: loadable(
      () =>
        import(/* webpackChunkName: "_css_MarkdownContainer" */ "./desktop"),
      {
        resolveComponent: (mod) => mod.MarkdownContainer,
      }
    ),
  },
```
Now let's look to `componentServer` and `componentClient` endpoints. 
(About new widget's name `MarkdownContainer_css` read in the middle of this article)

We allready have 2 webpack configurations: one for the client and one for the server, resulting in 2 builds. These endpoints we transform at webpack loaders: remove `componentClient` from server loader, and remove `componentServer` from client loader. (use `jscodeshift` for example)
Server and client part of widget is connected via identical chunk name `MarkdownContainer`.

`SSRC: true` - it is a flag for widgets factory. At this point we must remember, that it is an isomorphic code, and first time it is working at server, and second time - at client.

At the end of standard recursive factory func add:

```jsx
// server
if (SSRC) {
  reactElement = createElement(ServerTransformer, { SSRC }, reactElement);
}

// client
if (properties?.__html) {
  reactElement = createElement(ClientTransformer, { __html: properties.__html });
}
```

At server render, if widget's data has `SSRC` key, we create wrapper with prop `SSRC`.

At client render, if we have html-rendered string, we create client wrapper. 

Here `ServerTransformer` and `ClientTransformer` code:

```jsx
const ServerTransformer = ({ children }) => (
    <section>{children}</section>
);
```

```jsx
const ClientTransformer = ({ __html }) => (
    <section dangerouslySetInnerHTML={{ __html }} />
);
```
Component `FakeTransformer` just return null.

You see, that all transformers wrap codewith `section` tag. You can wrap by `<>` at server and `<section>` on client and change html by useEffect, but you will catch hydration mismatch.

Now, our build is ready to use. When a user requests the page, we need to perform some tasks on the server side and modify the server router.

When we transform JSON into a React tree, we need to perform some changes.

```jsx
const Renderer = ({...}) => (
  <Consumer>
    {({ extractor, widgets }) => {
      const res = createReactTree(widgets, device, ...);
      if (extractor) {
        checkSSRC(res, widgets, extractor);
      }
      return res;
    }}
  </Consumer>
);
```

Check tree and modify json state (it will sent to client at html body)
```jsx
const checkSSRC = (tree, widgets, extractor) =>
  (tree.length ? tree : [tree]).map((elem, tree_i) => {
    if (elem?.props?.children) {
      // Recursive
      checkSSRC(elem.props.children, widgets[tree_i].children, extractor);
    }
    if (elem?.props?.SSRC) {
      const extractorClone = Object.assign(
        Object.create(Object.getPrototypeOf(extractor)),
        extractor
      );
      let __html = renderToString(extractorClone.collectChunks(elem));
      // don't need any props now
      widgets[tree_i].properties = { __html };
      // don't need any children now
      widgets[tree_i].children = [];
    }
    return elem;
  });
```
As you see, we have an instance of css generator from current react node, to have a css classes. At the top of code sample, you have seen a new widget with `_css` postfix - it needs to get css-classes and styles for critical css. Without it, we only have classes at html, and no styles.

It's all done! Now we can check old/new HTML of the page, and see result.

HTML structure:
1. Critical CSS
2. HTML content
3. React state
4. Resources

**1. Critical CSS**
The same or less. If component has multi-designs, will be selected only needed classes/styles;

**2. HTML content**
The same, but server components wrapped with `<section>` tag. If you not need, you can add some code to `ClientTransformer` component (and have hydration mismatch?)

```jsx
useEffect(() => {
  if (!ref.current) return;
  // make a js fragment element
  const fragment = window.document.createDocumentFragment();
  // move every child from our div to new fragment
  while (ref.current?.childNodes[0]) {
    fragment.appendChild(ref.current?.childNodes[0]);
  }
  // and after all replace the div with fragment
  ref?.current?.replaceWith(fragment);
}, [ref]);
```

**3. React state**

Old
```json
{
  "name": "Markdown",
  "properties": { "content": "Have **bold** text" }
}
```

New
```json
{
  "name": "Markdown",
  "properties": {
    "__html": "\u003Csection\u003E\u003Cp class=\"a1jIK lG2mw _2G2mw a2WsA b2WsA\"\u003EHave \u003Cspan class=\"a1jIK w1jIK\"\u003Ebold\u003C\u002Fspan\u003E text\u003C\u002Fp\u003E\u003C\u002Fsection\u003E"
  }
}
```
(`__html`: This is a HTML-string, converted to JSON)
```html
<section>
  <p class="a1jIK lG2mw _2G2mw a2WsA b2WsA">
    Have <span class="a1jIK w1jIK">bold</span> text
  </p>
</section>
```

**4. Resources**

Less JS/CSS chunks.

Downloaded JS decreases from `656Kb` to `570Kb` (-**86Kb**, localhost, not minified data. In my case, was changed Text, Button and MarkDown widgets. ).

--- 

This article not a "Hello word" type, but I hope this help you to undestand some of server components, promote to know more about it, fix bugs and give me advises to optimize code...
and make the web fast again!

---

P.S. Look for my previous post [Reduce bundle size via one-letter css classname hash strategy](https://dev.to/denisx/reduce-bundle-size-via-one-letter-css-classname-hash-strategy-10g6)

---

The text was edited with AI to make the english stronger.

---

Donation: Ethereum 0x84914da79c22f4aC6fb9D577C73E29E4AaAE7622