惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

A
Arctic Wolf
博客园 - 聂微东
F
Fortinet All Blogs
云风的 BLOG
云风的 BLOG
小众软件
小众软件
V
Visual Studio Blog
博客园 - 三生石上(FineUI控件)
OSCHINA 社区最新新闻
OSCHINA 社区最新新闻
Apple Machine Learning Research
Apple Machine Learning Research
freeCodeCamp Programming Tutorials: Python, JavaScript, Git & More
The Cloudflare Blog
H
Hackread – Cybersecurity News, Data Breaches, AI and More
The GitHub Blog
The GitHub Blog
Y
Y Combinator Blog
Cyber Security Advisories - MS-ISAC
Cyber Security Advisories - MS-ISAC
博客园_首页
L
LangChain Blog
A
About on SuperTechFans
阮一峰的网络日志
阮一峰的网络日志
I
Intezer
T
The Blog of Author Tim Ferriss
Security Latest
Security Latest
C
CXSECURITY Database RSS Feed - CXSecurity.com
Know Your Adversary
Know Your Adversary
Simon Willison's Weblog
Simon Willison's Weblog
cs.CL updates on arXiv.org
cs.CL updates on arXiv.org
P
Palo Alto Networks Blog
Scott Helme
Scott Helme
S
Secure Thoughts
Spread Privacy
Spread Privacy
T
Threat Research - Cisco Blogs
Attack and Defense Labs
Attack and Defense Labs
P
Privacy & Cybersecurity Law Blog
O
OpenAI News
H
Heimdal Security Blog
www.infosecurity-magazine.com
www.infosecurity-magazine.com
Help Net Security
Help Net Security
C
Cyber Attacks, Cyber Crime and Cyber Security
Blog — PlanetScale
Blog — PlanetScale
GbyAI
GbyAI
G
Google Developers Blog
博客园 - Franky
cs.AI updates on arXiv.org
cs.AI updates on arXiv.org
K
Kaspersky official blog
Recent Commits to openclaw:main
Recent Commits to openclaw:main
T
Tor Project blog
D
Darknet – Hacking Tools, Hacker News & Cyber Security
T
Tenable Blog
Google Online Security Blog
Google Online Security Blog
PCI Perspectives
PCI Perspectives

Jim Nielsen’s Blog

Podcast Notes: Ed Catmull on David Senra Make It Work vs. Make It Good Podcast Notes: Iain McGilchrist on “The Great Simplification” What’s an Icon in 2026? Family Feud: Mac-assed Mac App Edition This Page Left Intentionally Blank Notes from Bryan Cantrill’s “Intelligence is not Enough” My Om Malik Story Blogging Can Just Be Stating The Obvious Consistency, But in Excellence Not Appearance Full Page Paralysis Being “Good” at Things Coding Is Designing An Ode to the Exacting Pedantry of Computers Book Notes: “Poor Charlie’s Almanack” Something’s Rotten in the State of macOS Icon Design Building Software Requires Digestion Out With the JS, In With the HTML Reminder: You Can Stitch Together Lots of Little HTML Pages With Navigations For Interactions Collective Speed Is Not the Summation of Individual Speed Hook It Up to the Machine Speed is Not Conducive to Wisdom That’s a Skill Issue Fewer Computers, Fewer Problems: Going Local With Builds & Deployments
Making a Shuffle Button
Jim Nielsen · 2026-07-06 · via Jim Nielsen’s Blog

I made some updates to my notes blog, including a change to how my “Shuffle” feature worked.

Figured I’d blog about it.

Shuffle? On a Blog?

At the time of this writing, I have 974 “notes” that I’ve published.

For fun, I have a “shuffle” button that digs up a random note from the past. I like to press it from time to time and re-encounter some insight from the past.

It’s like going through an old album, pulling out a random photo, and thinking, “Oh yeah, I remember this! Good times.”

Like old photos, there’s also the occasional “that didn’t age so well”.

But I find it fun to randomly dig up old insights from others and continue to be inspired.

How Did It Work Before?

Since my site is built and hosted as static files without a runtime server, this feature required JavaScript to work.

Every page had a snippet like this:

<!-- In the site navigation -->
<button id="js-shuffle">Shuffle</button>

<!-- Way down at the end of the HTML -->
<script>
  // All 974 note IDs injected by my SSG
  const noteIds = ['id-1', 'id-2', id-3', 'id-4', '...'];
  document.querySelector("#js-shuffle")
    .addEventListener('click', () => {
      // randomly grab an item in `noteIds`
      const randomId = '...';
      window.location.href = `/n/${randomId}/`
    })
</script>

Essentially: inject every note ID into every HTML page and, when the shuffle button is clicked, randomly grab one and navigate the user to it.

Not the most elegant thing, but it worked.

The problem was that every time I published a new post, every single page had to be re-uploaded to Netlify because every file’s hash would change and its etag/cache was invalidated.

This made my builds slow. It also made it difficult, from a development perspective, to ensure refactors didn’t result in unexpected changes to output (using Dev.changes from my SSG web origami).

So I decided to make a change.

What About a No-JS Approach?

Because I love to see if I can make things work without JavaScript, I had the thought to randomly write the href at build time using my SSG, which would result in output like this:

<!-- /notes/1 -->
<a href="/notes/3">Shuffle</a>

<!-- /notes/2 -->
<a href="/notes/1">Shuffle</a>

<!-- /notes/3 -->
<a href="/notes/2">Shuffle</a>

And every time I re-build my site, just have this logic run on the static site generator so that it’s different href for every page, every time.

Pros:

  • Doesn’t require JavaScript
  • Doesn’t require a server (request-time logic)

Cons:

  • File hashes change across builds (even if there’s no new content or template changes, every HTML page now has a different href for the shuffle link for every build). This makes deployments way slower because Netlify has to redeploy every file on every build. Plus Etags change so caching is basically ineffectual.

I decided I didn’t want to do this, so on to JavaScript!

How Does It Work Now?

My first thought was to create a single JSON file that contained all my note IDs. Then when the “Shuffle” button gets clicked, I fetch that, grab a random ID, and navigate the user, e.g.

<!-- In the site navigation -->
<button id="js-shuffle">Shuffle</button>

<!-- Way down at the end of the HTML -->
<script type="module">
  const noteIds = await fetch("/notes.json").then(...);
  // Handler code for when button is clicked
  // grab a note ID & navigate to that location
</script>

This would work. It localizes the caching issue to a single file, so only one file has to be invalidated/re-uploaded across builds.

But in playing with it a little more, I decided to try something a little more...unconventional.

I’ve written before about having lots of little HTML pages and I thought, “Can I put this functionality in a single HTML page rather than a JSON file?”

And what I ended up with was a link, e.g.

<a href="/shuffle/">Shuffle</a>

That when clicked navigates the user to a new page. That page has all the JS logic embedded in it, e.g.

<!-- In the site navigation -->
<span>Shuffling...</span>

<!-- In the body of the page -->
<noscript>JS is required to use the Shuffle functionality</nocript>
<script>
  // All 974 note IDs injected by my SSG
  const noteIds = ['id-1', 'id-2', id-3', 'id-4', '...'];\

  // A slight delay so user sees "Shuffling..." effect
  setTimeout(() => {
    // randomly grab an item in `noteIds`
    const randomId = '...';
    window.location.href = `/n/${randomId}/`
  }, 300);
</script>

There are a few things I like about the experience this implementation provides.

First: shuffle is a route, so I can navigate to it directly without using the GUI, e.g. notes.jim-nielsen.com/shuffle

Second: I handle the UI/X with a slight delay to make it appear like something is happening when you click the button. If you click the button and it immediately jumps to the next, randomized page, it almost seems to happen too fast. Like you’re left with this feeling of “What just happened?”

But in this scenario, it navigates you to the “Shuffle” page, the button you just clicked turns into a spinner + text indicating something is happening, and there’s a slight (intentional) delay before the JS executes and sends you to a randomized note.

I know it’s a bit weird. “Introduce artificial slowness? Are you crazy?” But I like it. It feels like the shuffle feature on an old music player.

I remember one of my CD players had a “Shuffle” feature. When I’d click the button, it would display “Shuffling…” on the little black and white screen and you’d encounter this brief state where (I presume) the lens inside the hardware would move along the physical track to the spot where it would start reading a new, random song from the CD.

The hardware constraints necessitated this kind of an experience, but I always liked it because it felt like the CD player was “thinking” about what track to pick next. This state clearly conveyed to me that my intent to shuffle was received and being followed. I liked that feedback, and it’s exactly what I wanted to do on my notes site (even though it was completely unnecessary).

Screenshot of three routes side-by-side indicating a shuffle-like navigation between them.

I like having that brief moment of feedback where it’s very clear that your intention was received and being followed, vs. having it happen so fast you can’t even perceive precisely what happened.

Here’s a video to show it in action:

Why?

I know that’s a lot of information for something so small — and, arguably, unnecessary.

But I still enjoy writing about how I make decisions when I build things for myself.

Hence this post.