Thanks for reading my newsletter ❤️. This edition was delivered on Monday, February 17, 2025.

Sunday, February 16, 2025

Hydration In React

Just for my own learning today I had a bit of a play around with React hydration and server side rendering (SSR).

Whilst I've used frameworks like Next.js and Remix I've never just done the "from scratch" implementation of React SSR.

Here were the steps I followed:

  1. Implement a basic GET endpoint, I'm using Elysia and bun, the call to html() essentially just calls the react-dom/server renderToString function and returns a Response
app.get("/test/ssr", async () => {
  const response = html({
    title: "Test SSR",
    meta: {
      description: defaultDescription,
      "theme-color": "#1c1917",
    },
    links: [],
    scripts: [{
      src: "/client.js",
      defer: true,
      type: "module",
    }],
    body: <Test />,
    styles: [GLOBAL_CSS],
  });

  return response;
})
  1. At this point all things are pretty standard, a GET request is sent to /test/ssr and a response is sent back to the client as HTML
  2. Note the /client.js file that's sent back to the browser as a script
<head>
	<script src="/client.js" type="module" defer=""></script>
</head>
  1. This script loads after the HTML renders, it contains a bunch of React code which I've created using a build step
  2. Before the build step the client.js is actually a client.tsx file which looks like this
/// <reference lib="dom" />
import React from "react";
import { createRoot, hydrateRoot } from "react-dom/client";
import { Test } from "./pages/test"

const rootElement = document.getElementById("root");

if (!rootElement) throw new Error("Root element not found");

if (rootElement.innerHTML.trim().length) {
  hydrateRoot(rootElement, <Test />);
} else {
  createRoot(rootElement).render(<Test />);
}
  1. The <Test /> component has some tanstack-query setup and some basic React state
import React, { useState } from "react";
import { Something } from "../components/something";
import { Nav } from "../components/nav";
import {
  QueryClient,
  QueryClientProvider,
} from '@tanstack/react-query'

export const Test = () => {
  const [count, setCount] = useState(0);

  const queryClient = new QueryClient()

  return (
    <QueryClientProvider client={queryClient}>
      <div className={`max-w-xl mx-auto p-4 text-opacity-80 font-serif`}>
        <Nav />
        <button onClick={() => setCount(count + 1)}>Increment</button>
        <p>{count}</p>
        <Something />
      </div>
    </QueryClientProvider>
  );
}
  1. The <Something /> component actually uses tanstack-query with some sleep logic to replicate an API call
import { useQuery } from "@tanstack/react-query";

const sleep = (ms: number): Promise<void> => {
  return new Promise((resolve) => setTimeout(resolve, ms));
};

export const Something = () => {
  const { data, isLoading } = useQuery({
    queryKey: ["something"],
    queryFn: async () => {
      await sleep(5000);
      return "Something";
    },
  })

  return isLoading ? <p>Loading...</p> : <p>{data}</p>
}
  1. I need to bundle this code up so that the browser can interpret the JSX that I'm writing, I can use the bun build CLI tool to do this, I just need to pass the parent file (client.tsx) to the build tooling as it'll also bundle up the children, note that I'm also using uglifyjs to minify the JS making the resource size as small as possible
bun build ./client.tsx --outdir ./dist && uglifyjs ./dist/client.js --compress --mangle --output ./dist/client.js
  1. I then have a /client.js endpoint in my Elysia web server
app.get("/client.js", async () => {
  const path = "./dist/client.js";

  const file = Bun.file(path);

  const text = await file.text();

  return new Response(text, {
    headers: {
      "Content-Type": "text/javascript",
    },
  });
});

When the client.js file executes in the browser the React hydration process occurs. The server HTML is compared to what React renders on the client. If this matches up you're all good. If it doesn't match up you'll get an error.

But essentially any code that's a child of client.tsx you can treat like traditional SPA style React. You have useState and you can handle async things inside of useEffect. You can use tools like tanstack-query or zustand for state management.

Wednesday, February 12, 2025

Noojee#instagram.com

This looks like a fun family day for me, Emma and Boots to do.

Scorebug#daringfireball.net

Love a good nerd out over a scorebug.

I like the new NFL one that's being talked about here.

I noticed the change immediately (of course), and my knee-jerk instant reaction was negative. Too big, too bold, too different. Don’t like. That’s natural. Human beings evolved to be alarmed by change. If everything looks the same as usual, everything is probably as safe as usual. If something looks jarringly different, it might be a sign that you’re about to be killed. (Evolutionarily speaking.)

So I started studying and considering the changes to Fox’s scorebug. I quickly not only warmed up to the new scorebug, I decided I really like it. It’s better than Fox’s old one, and better than every other network’s (which all largely look the same), in almost every single regard.

The AFL aught to freshen up it's scorebug too. My favourite of all time is still the 1999 era one (the one that has a gold/brown border). In saying this, adopting something new like what the NFL has done here would be interesting.

Pine Barrens

These last few weeks I've truely become addicted to The Sopranos.

Season 1 and 2 are fantastic but what really has me hooked is season 3. The epitome of the sheer chaos and edge of your seat viewing is the Pine Barrens episode.

The Jackie Jr storyline, Gloria and Tony, Christopher and Paulie getting stuck in the woods, the humour of Bobby; there is just so much plot to hook you in.

I feel like Pine Barrens is The Sopranos version of The Rains of Castamere. This backed up by the fact that both of these episodes are towards the end of their season 3's.