Introducing Seeded Randomisation into an SSR Gatsby Project

Hero image for Introducing Seeded Randomisation into an SSR Gatsby Project. Image by Jametlene Reskp.
Hero image for 'Introducing Seeded Randomisation into an SSR Gatsby Project.' Image by Jametlene Reskp.

One of the more recent topics of discussion with my airline client has been around the equal prioritisation of content, and how we can avoid an excessively long and large page whilst still giving items of content equal billing on their homepage.

In this instance, we're talking specifically about the different destinations that the airline flies to. Attempting to find a way to display information for more than a dozen locations at once without overloading the user or resulting in a chaotic mess of content is difficult. This is made doubly so when, in reality, the content creators actually want to display more than thirty or forty locations at once.

The interim solution, for now, has been a carousel (even though I'm not at all a fan of carousels), which allows the content creators free reign to include or exclude as many items as they wish, safe in the knowledge that they are all there and accessible for everyone:


An Argument for Randomisation

There is an alternative, though, and it's something I have been a bit of a fan and advocate of for a very long time: randomisation.

In the dynamic world of web development, randomised content serves as a way to present different items to your users fairly and equally. I spent much too much time finding ways to introduce randomisation into one of the previous versions of my portfolio simply to add a level of novelty to what was otherwise a fairly mundane grid. What it did mean however is that a visitor would be just as likely to be presented with the work I did for the London 2012 Olympics in that key topleft corner as they were to see my work with the Christian festival Rock Nations in that top spot. I even strongly believe that randomisation helps with SEO, offering signals back to search engines that the content is fresh and changes regularly.

This is something that I carry on across my personal (and client) projects even now; the order of the projects that flicker past your eye as you first load my homepage is random, as are the Featured Projects a few scrolls downwards.

By finding ways to introduce randomness into your website, you add an element of interest that keeps the experience fresh and engaging, encouraging and driving user exploration deeper into the site whilst ensuring a balanced exposure of all content.


The Challenge: Randomisation in SSR

This is all well and good, but when it comes to developing an application or website that relies on ServerSide Rendering (SSR), randomisation poses a significant problem and a unique set of challenges. At their core, traditional clientside randomisation methods such as Math.random() simply are not compatible with an SSR environment.

When an SSR site is built (as is the case with Gatsby), the server prerenders pages into static HTML which ensures quick load times, ideal for a good user experience (and for SEO). However, if these pages contain elements that are randomised using methods like Math.random(), then the version that the server generates will be different across each render, and different again to the version that the clientside JavaScript then attempts to rehydrate the page with.

This leads to hydration errors and a phenomenon known as a "mismatch".

Screenshot from Chrome Dev Tools' console displaying React SSR Hydration errors: minified errors #418, #423, and #425.

The Importance of Consistent Rendering

This mismatch between serverrendered and clientrendered content can be more than just a cosmetic issue; it can lead to a jarring user experience as elements appear to change abruptly on the page during clientside rendering. At worst, this leads to the entire page being painted by the browser for a second time as part of the hydration process. At best, this is inelegant.

As SSR becomes a more routinely accepted and expected way of developing dynamic websites, there is also evidence that suggests that this inconsistency can be confusing for users and may lead to a negative impact on SEO.

The Challenge

So, the challenge is to introduce randomisation into a website but in a way that is consistent and predictable across both the server and client sides. The goal is to have the server and the client render the same randomised content for each user visit, each time.

Really, the challenge is to find a predictable, consistent, and nonrandom way of making things look random...

As you might appreciate, this is the exact opposite of the very definition of 'random', but it can still result in something extremely similar.


The Solution: Seed‑Based Pseudorandomness

Embracing Determinism

The key to reconciling randomisation with the deterministic nature of ServerSide Rendering lies in a concept known as seedbased randomisation (and I use the term 'randomisation' loosely from here on out).

Traditional random number generators produce completely unpredictable results (although even Math.random() isn't truly random), whereas using a seedbased approach means we can generate random numbers that are predictable and repeatable when provided with the same initial 'seed'.

The Power of Using Dates as Seeds

The issue when it comes to SSR versus clientside is finding a stable seed, which is where using the date comes in...

By using the current date (or a format of it) as the seed, we can ensure that the randomisation process yields the same results across both server and client renders on a given day (or month, or hour).

As an example, formatting the current date as YYYYMMDD provides a dailychanging seed, so although your content may remain in the same 'random' order for the day (arguably, this is preferable from a usability perspective anyway), anybody revisiting your application the next or another day will be presented with an allnew ordering and hopefully some fresh content that they did not see the day before.

Using this approach, where we align the 'randomish' outputs from both the serverside and clientside, we can start to address the core issue in SSRmismatch. Quite simply: that they need to output the same thing...

Practical Applications and Flexibility

The beauty of this solution is its flexibility. Depending on your specific need, the seed can be adjusted to change more frequently (e.g., hourly) or less frequently (e.g., weekly), allowing precise control over how often the randomised content changes.

This makes seedbased randomisation using dates a powerful tool if you want to add a dynamic yet consistent element to your Gatsby/SSR project.


Step‑By‑Step Implementation

Integrating seedbased randomisation into a Gatsby project is relatively straightforward and can be accomplished with native JavaScript, allowing you to develop a reusable utility function that can be imported and used across your application.

I should say here that whilst there are also libraries like seedrandom available for more complex needs, most of the time this is simply going to import way more weight to your project than is necessary. A basic implementation can be done without thirdparty dependencies.

Creating a Seed‑Based Randomisation Function

1. Generating a Date‑Based Seed:

The first step is creating a function that we can use to generate our seed. Consistency across time zones is important here to make sure that we don't end up in a scenario where the client side is in a different time zone than our server, so we'll use the UTC date as our seed:

const getUtcDateSeed = (): number => {  const now = new Date();  const seedString =    now.getUTCFullYear().toString() +    (now.getUTCMonth() + 1).toString().padStart(2, '0') +    now.getUTCDate().toString().padStart(2, '0');  return parseInt(seedString, 10);};

Here, we're generating a datebased number based on the current (UTC) date in the format YYYYMMDD. We can then use this number as a seed for our pseudorandomisation.

2. Implementing a Basic Seed‑Based Random Generator:

Once we have our seed, we need to create a simple random number generator function. Here's a basic implementation:

const seededRandom = (seed: number): (() => number) => {  let hash = seed;  return () => {    hash = (hash * 9301 + 49297) % 233280;    const x = Math.sin(hash) * 10000;    return x - Math.floor(x);  };};

Here, we're generating a pseudorandom number between 0 and 1. When the same seed is used as a starting point, it will always return the same (random) number, making it appear both random but reproducible.

This works by first applying a simple mathematical formula to the seed: (hash = (hash * 9301 + 49297) % 233280).

This formula is designed to efficiently scramble the seed, using the parameters of a linear congruential generator (LCG), and creating a new number that seems unrelated to the previous one. The use of 9301, 49297, and 233280 is somewhat arbitrary you can change these to any number you want. However, the combination of these numbers in the formula affects how well the algorithm distributes values, which is why you will see most hash implementations of this type using those specific values.

Once the seed has been hashed, our function uses Math.sin() to manipulate the number even more, ensuring that the final output is a fractional between 0 and 1.

3. Using It in Your Project

It may well be that you only ever want to generate random(ish) values based off the date, so having these as two separate functions may be a little irrelevant for your use case. I tend to prefer to keep them separate like this, though, simply because it allows a little more flexibility.

So, to generate a random (but the same for the day) number in your Gatsby project, you would do something a little like this:

const seed = getUtcDateSeed();const randomNumber = seededRandom(seed);

Optional: Using Third‑Party Libraries

I touched on this a little further up the page here, but if you have particularly complex randomisation requirements, you might also consider using a thirdparty library like seedrandom. These types of libraries can offer more advanced features and better statistical randomness. However, for most use cases, the above method is more than enough.


Advanced Tips

Aligning Cache with Seed Change Frequency

One of the more nuanced aspects of implementing seedbased randomisation, especially when using dates as seeds, is aligning this approach with your website's caching strategy. Caching can inadvertently lead to inconsistencies if not properly aligned with the seed change frequency.

  • Daily Seed Change

    : If your seed changes daily (using a YYYYMMDD format for example), you need to ensure that your cache expiration aligns with this cycle. Ideally, the cache should be refreshed or invalidated at the start of each new day (in UTC time, if you're following my example from above) to correspond with the seed change. This prevents a scenario where a user receives a cached page with the previous day's randomised content.
  • Custom Seed Frequencies

    : If you're using a custom frequency (e.g., changing the seed every hour or week), adjust your caching strategy accordingly. This ensures that users always receive the most current version of randomised content as per the seed schedule.

This also means that you need to ensure that your serverside content is rendered at least once a day. I tend to use a repeating cron job to kick off a new build just past midnight every day,

Handling Client‑Side Randomisation Post‑Load

Whilst seedbased randomisation is excellent for initial page loads, you might also want to introduce randomness in clientside interactions after the page has loaded. This can be achieved through additional JavaScript that runs in the browser, independent of the serverside seed.

  • EventTriggered Randomisation

    : Implement randomisation on clienttriggered events like clicks, scrolls, or page interactions. Since these events occur postload, they won't conflict with the serverside rendered content. You can use standard Math.random() for these purposes, as the need for SSR consistency does not apply here.
  • Periodic Content Updates

    : For dynamic content that updates regularly (like news feeds or social media posts), you can use clientside JavaScript to fetch and display new content at set intervals. This can also be randomised using clientside seeds or simply Math.random(), adding a layer of dynamism to the user experience.
  • UserSpecific Randomisation

    : Consider incorporating userspecific elements (like time of day or user preferences) in your clientside randomisation logic. This can make the user experience more personalized and engaging.

Wrapping up

Using randomness in your Gatsby (and other SSR'd) projects needn't lead to hydration issues. Using datebased seed randomisation can offer a unique blend of dynamism and consistency whilst maintaining the integrity of ServerSide Rendering (SSR).

This approach solves the challenge of displaying randomised content predictably, ensuring that all users experience the same version of the site at any given moment, despite the underlying randomness.

Using a datebased seed, particularly with adjustments for time zones and cache alignment, ensures that the content remains consistent across various user visits.

As with any web development technique, the real potential of seedbased randomisation really only comes to the surface through experimentation; my project needs and yours may differ wildly, but with each project presenting unique challenges and opportunities, they also present the opportunity to use randomisation in this way.


Categories:

  1. Development
  2. Gatsby
  3. JavaScript
  4. Node.js
  5. React
  6. Server‑Side Rendering