You may not need SSR: Using localStorage for personalization
Tags: ssr, webdev, programming, staticwebapps
Published: 2023-02-10 20:33:13

## TL;DR

Instead of using server-side rendering (SSR) to make small client state-based adjustments to the pages you are serving, you can use localStorage to instantly set values - before anything is shown to the user:

```html
<div data-username>User</div>
<script>
  const username = localStorage.getItem("username");
  if(username !== null) {
    document.querySelector("[data-username]").innerText = username;
  }
</script>
```

I cover what to be aware of and how to make it work well below.

---

Imagine this: you have a fully static website, but you are starting to introduce more dynamic content on it. You'd like logged in users to be shown their name in the top-right of the navigation bar. A classic!

You are adding limited dynamic parts to the website, which can easily run as components embedded in the static site.

You have two options for showing the username. Turning the whole website into a single-page application (SPA) seems like overkill. And server-side rendering (SSR) - maybe on the edge - is a lot to set up just for this.

---

Where old-school SSR like Ruby on Rails apps relied on central servers, and scaling involved all sorts of manually configured caching layers, the new generation renders *on the edge*.

In edge computing, your content-delivery network (CDN) executes SSR code in a server farm extremely close to your end-user. That means you get really fast response times, even as you get to personalize content.

Generally, modern frameworks (like [Next.js](https://nextjs.org/docs/api-routes/edge-api-routes), [Nuxt.js](https://nuxtjs.org/docs/concepts/server-side-rendering/), [Astro](https://docs.astro.build/en/guides/server-side-rendering/) and [Eleventy](https://www.11ty.dev/docs/plugins/edge/)) help a lot with the setup of edge computing. Even so, computing at the edge adds extra costs to your infrastructure bill, and while it is fast it can never be as fast as just serving a static file.

We've been down the path of SSR before. When Ruby on Rails and Wordpress were the cat's pajamas, every site became server-side rendered. The most common way to run a website today is still on Wordpress. And it is important to remember that static site generators (SSGs) were a response to the inefficiencies of needless server-side rendering.

If you can do without SSR, you probably should. At least, it's important to have more tools in your toolbox.

Here's one such tool.

---

This tool is basically remembering the oft-overlooked fact that *you can just run plain Javascript in the browser* -- you don't need a framework.

If you place a `script`-tag with Javascript code in it in your HTML, it is executed when the browser reaches and parses it. If your Javascript executes instantly, the changes it makes will take effect before the user sees anything - they won't know the Javascript changed anything.

To make it run instantly you need to avoid I/O operations like HTTP calls. But localStorage runs instantly from memory, and doesn't require I/O. This leads us to a cool trick:

```html
<div data-username>User</div>
<script>
  const username = localStorage.getItem("username");
  if(username !== null) {
    document.querySelector("[data-username]").innerText = username;
  }
</script>
```

This code executes right after the `div` is made available to the DOM. It tries to get an item with the key `"username"` from localStorage. If that succeeds, it sets the inner text of the `div` to whatever value that is---otherwise it keeps the text `"User"` that is in the HTML.

This executes in the browser, and is much more time and cost efficient than either running an SPA or using SSR.

---

You probably want something a little more flexible than the above example. For example, what if the `"username"`-item in localStorage changes, as the user logs in on the website?

I've made a small library called [*one-way binding system* (owbs)](https://www.npmjs.com/package/owbs) that helps make things like this easier. It is made for being embedded inline into HTML pages (< 1 kb) so it runs and can be before Javascript bundles load.

With owbs you an easy way to bind localStorage values to DOM-elements. When you update a localStorage value through owbs, all the bindings are updated too.

```html
<script>
  /* inline owbs.min.js here - less than 1 kb of code */
</script>

<!-- ... -->

<div data-username>User</div>
<script>
  owbs.bind("username", "[data-username]");
</script>
```

Here's the syntax for updating a value:

```js
owbs.val.username = "New username";
```

Unlike native localStorage, owbs supports complex values like objects or arrays out of the box - just assign them to a `owbs.val` field. It also lets you map values while binding or assign easily to attributes instead of text contents of a DOM element:

```js
owbs.val.bestFriends = [
  { id: "csage", name: "Carl Sagan" },
  { id: "bobm", name: "Bob Marley" },
];

// ...

owbs.bind(
  "bestFriends",
  "[data-friend-list]",
  (friends) => friends.map(({ id, name }) => `
    <div class="friend" data-friend-id="${id}">
      ${name}
    </div>
  `),
  "html", //<-- set `innerHTML` instead of `innerText`
);
```

Check the [owbs reference](https://www.npmjs.com/package/owbs#reference) for more usage information.

---

As an industry we have a tendency to get a tad too obsessed with cool hammers. Suddenly, everything looks like a nail we can punch in!

When old-school SSR---which had become the default for every website---proved too slow and scaled too poorly, developers moved on to SPAs. Suddenly *everything* had to be an SPA!

The movement back to static sites (when possible) and limited dynamic components was some welcome nuance. I hope we don't overdo it now that many of the modern frameworks are adding support for SSR on the edge.

I'm a firm believer in using the right tool for the job, which means mixing and matching practices depending on the needs to the application being built. This effectively rules out SPAs for most sites - it is simply too all-encompassing.

Some things can be static. Sometimes we need dynamic components. Sometimes a little bit of SSR is the best way to go - on the edge or centrally. To find the right tool for the job, we have to do the hard work of actually analyzing the needs and requirements for the system we are building.

The concept of [Islands in the Astro framework](https://docs.astro.build/en/concepts/islands/) is a great example of allowing some dynamic content blocks on an otherwise static site. This approach mixes well with [owbs](https://www.npmjs.com/package/owbs) by keeping large parts of the site static (with minor personalization via localStorage), while still allowing more dynamic experiences when needed. I think it's a promising direction I hope more sites will move in, so we don't need full page hydration for every page.

I hope this little tool and approach can help you avoid bloating your users' network connection with huge SPA bundles -- and maybe you can put off learning SSR on the edge for a few months until you have a really good use case for it!

---

If you have any cases you want my input on, whether they would be good cases for this pattern or not, ask me in a comment below!

---

*Niels Roesen Abildgaard is Staff Software Consultant and Co-founder at [deranged](https://deranged.dk), a team of developer trainers, coaches and software engineers that integrate with and help improve other teams' performance. Leaving a sustainably stronger team, every time.*