Fast Pages with React
Tags: webdev, react, performance, javascript
Published: 2022-06-30 08:02:02

*Photo by <a href="https://unsplash.com/@rockthechaos?utm_source=unsplash&utm_medium=referral&utm_content=creditCopyText">Kolleen  Gladden</a> on <a href="https://unsplash.com/s/photos/performance?utm_source=unsplash&utm_medium=referral&utm_content=creditCopyText">Unsplash</a>*

I recently created the [website for my book "The Art of Micro Frontends"](https://microfrontends.art/). For this page I took a rather conservative approach - making a "true" single page (i.e., landing page) that should be as approachable and fast as possible - without sacrificing developer experience.

Surely, there are right now quite some frameworks and tools out there. But I did not want to spent countless hours learning new stuff just to be blocked by some framework restrictions. Instead, I've chosen an approach that - in my opinion - is quite convenient, super fast, and very lightweight.

## The Tech Stack

I've chosen to use `react` as library for writing reusable components. In a nutshell, for the page it allows me to have code like the following:

```jsx
function Content() {
  return (
    <>
      <Header />
      <Grid>
        <Book />
        <Author />
        <Buy />
        <Outline />
        <Reviews />
        <Articles />
        <Examples />
        <Shops />
        <Talks />
        <Videos />
        <Links />
      </Grid>
      <Footer />
    </>
  );
}

export default Content;
```

This is very easy to write, change, and align. As far as styling is concerned I've installed `styled-components`. This allows me to have the CSS next to the component where it should be applied. In a nutshell, this makes writing reliable CSS very easy. Also, when I omit (or even throw out) components in the future their CSS won't be part of the output.

For instance, the `Grid` component shown above is defined like:

```jsx
const Grid = styled.div`
  display: grid;
  grid-column-gap: 1.5rem;
  grid-gap: 1.5rem;
  grid-row-gap: 0.5rem;

  @media only screen and (max-width: 999px) {
    grid-template-areas:
      'book'
      'buy'
      'outline'
      'author'
      'reviews'
      'articles'
      'talks'
      'videos'
      'examples'
      'shops'
      'links';
  }

  @media only screen and (min-width: 1000px) {
    grid-template-areas:
      'book       author'
      'buy           buy'
      'outline   outline'
      'reviews   reviews'
      'articles   videos'
      'articles examples'
      'articles    shops'
      'talks       links';
    grid-template-columns: 1fr 1fr;
  }
`;
```

Theoretically, the grid layout could also be computed via JavaScript - just giving the parts that are included (which is another reason why the CSS-in-JS approach is great here). For now, I am happy with the hard-wired layout.

Personally, I always like to have an additional set of checks for my applications, which is why I use the whole thing with TypeScript. TypeScript can also handle JSX quite well, so there is no need for anything else to process the angle brackets.

## Dev Setup

For the whole mechanism to work I use a custom build script. The file `src/build.tsx` essentially boils down to this:

```jsx
const root = resolve(__dirname, '..');
const dist = resolve(root, 'dist');
const sheet = new ServerStyleSheet();
const body = renderToStaticMarkup(sheet.collectStyles(<Page />));
const dev = process.env.NODE_ENV === 'debug' ? `<script>document.write('<script src="http://' + (location.host || 'localhost').split(':')[0] + ':35729/livereload.js?snipver=1"></' + 'script>')</script>` : '';

const html = `<!DOCTYPE html>
<html lang="en">
<head>
  ...
  ${sheet.getStyleTags()}
</head>
<body>${body}${dev}</body>
</html>
`;

sheet.seal();

addAssets(resolve(__dirname, 'static'));

addAsset(Buffer.from(html, 'utf-8'), 'index.html');

writeAssets(dist);
```

Most importantly, the `collectStyles` from `styled-components` create the inline stylesheet we'd like to use for this page. The `dev` variable keeps a small refresh script that will only be part of the page during local development.

For running the `build.tsx` file we use `ts-node`. By calling `ts-node src/build.tsx` we can start the process. A few other tools that are helpful for making this a great experience are:

- **LiveServer** for reloading during development (i.e., the script above already uses that)
- **Nodemon** for detecting changes during development (i.e., once we touch a file the `ts-node` process should restart)
- **HttpServer** for running a local webserver during development (i.e., we need to serve the page from somewhere - `http-server dist` is good enough for us)

All these tools can be wired together via `concurrently`:

```sh
concurrently "livereload dist" "http-server dist" "nodemon"
```

So when a file changes we have:

1. `nodemon` detecting the change and restarting `ts-node`
2. The output being placed in `dist`
3. `livereload` detecting a change in `dist` and updating the parts that changed

The whole thing is served from `http-server`. The configuration for `nodemon` looks as follows:

```json
{
  "watch": ["src"],
  "ext": "ts,tsx,json,png,jpg",
  "ignore": ["src/**/*.test.tsx?"],
  "exec": "NODE_ENV=debug ts-node ./src/build.tsx"
}
```

One last remark on the dev setup; for getting the assets in a set of custom Node.js module handlers is used:

```ts
function installExtension(ext: string) {
  require.extensions[ext] = (module, filename) => {
    const content = readFileSync(filename);
    const value = createHash('sha1').update(content);
    const hash = value.digest('hex').substring(0, 6);
    const name = basename(filename).replace(ext, `.${hash}${ext}`);
    assets.push([content, name]);
    module.exports.default = name;
  };
}

extensions.forEach(installExtension);
```

Each asset will be added to a collection of assets and copied over to the `dist` folder. The asset is also represented as a module with a default export in Node.js. This way, we can write code like:

```js
import frontPng from '../assets/front-small.png';
import frontWebp from '../assets/front-small.webp';
```

without even thinking about it. The assets are all properly hashed and handled by Node.js. No bundler required.

## CI/CD

For deploying the page I use GitHub actions. That is quite convenient as the repository is hosted anyway on GitHub.

The whole workflow is placed in the *.github/workflows/node.js.yml* file. There are two important steps here:

1. Build / prepare everything
2. Publish everything (right branch is `gh-pages`)

For the first step we use:

```yml
- name: Build Website
  run: |
    npm run build
    echo "microfrontends.art" > dist/CNAME
    cp dist/index.html dist/404.html
```

which automatically prepares the custom domain using the special `CNAME` file. All the output is placed in the `dist` folder. This will be then pushed to the `gh-pages` branch.

Likewise, I decided to make a copy of `index.html` with the `404.html` file. This file will be served if a user goes to a page that is not there. Such a mechanism is crucial for most SPAs - in this case we'd not really need it, but it's better than the standard GitHub 404 page.

The second step then pushes everything to the `gh-pages` branch. For this you can use the `gh-pages` tool.

```yml
- name: Deploy Website
  run: |
    git remote set-url origin https://git:${GITHUB_TOKEN}@github.com/${GITHUB_REPOSITORY}.git
    npx gh-pages -d "dist" -u "github-actions-bot <support+actions@github.com>"
    env:
      GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
```

Importantly, you need to specify the `GITHUB_TOKEN` environment variable. This way, the command can actually push code.

Now that's everything for the pipeline - the page can go live and be updated with every push that I make.

## Performance

So how does this little page perform? Turns out - quite well. You can go to [web.dev/measure](https://web.dev/measure/?url=https%3A%2F%2Fmicrofrontends.art) to check for yourself.

![Performance measured on web.dev](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/fmggitzht544h4xwxsgt.png)

To get 100 in each column also some tricks need to be applied. For instance, instead of just using something like an `img` tag you should use `picture` with multiple sources. That was another reason why choosing `react` was quite good:

```jsx
interface ImageProps {
  source: string;
  fallback: string;
  alt?: string;
  width?: number;
  height?: number;
}

function getType(file: string) {
  return `image/${file.substring(file.lastIndexOf('.') + 1)}`;
}

function Image({ source, fallback, alt, width, height }: ImageProps) {
  return (
    <picture>
      <source srcSet={source} type={getType(source)} />
      <source srcSet={fallback} type={getType(fallback)} />
      <img src={fallback} alt={alt} width={width} height={height} />
    </picture>
  );
}

export default Image;
```

With this little component we can write code like

```jsx
<Image
  source={frontWebp}
  fallback={frontPng}
  alt="The Art of Micro Frontends Book Cover"
  width={250}
  height={371}
/>
```

which will be applied just as mentioned. Also, quite importantly we specify the width and height of the image. In theory, we could also compute that on the fly when rendering - but as the page only has 3 images it really was not worth the effort.

## Conclusion

Writing simple sites does not need to be complicated. You don't need to learn a lot of new stuff. Actually, what is there already will be sufficient most of the time.

The page I've shown easily gets the best score and performance - after all its the most minimal package delivered with - for what it does - the optimal dev experience.

The code for the page can be found [on GitHub](https://github.com/ArtOfMicrofrontends/artofmicrofrontends.github.io/).