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

推荐订阅源

D
DataBreaches.Net
SecWiki News
SecWiki News
博客园_首页
人人都是产品经理
人人都是产品经理
博客园 - 聂微东
P
Palo Alto Networks Blog
V
Vulnerabilities – Threatpost
Project Zero
Project Zero
WordPress大学
WordPress大学
NISL@THU
NISL@THU
酷 壳 – CoolShell
酷 壳 – CoolShell
P
Privacy & Cybersecurity Law Blog
Jina AI
Jina AI
AWS News Blog
AWS News Blog
Scott Helme
Scott Helme
Martin Fowler
Martin Fowler
C
Cybersecurity and Infrastructure Security Agency CISA
Forbes - Security
Forbes - Security
H
Heimdal Security Blog
小众软件
小众软件
I
Intezer
A
Arctic Wolf
奇客Solidot–传递最新科技情报
奇客Solidot–传递最新科技情报
K
KPMG report finds enterprise disconnect between AI and its ROI | CIO
O
OpenAI News
S
Security Affairs
阮一峰的网络日志
阮一峰的网络日志
Latest news
Latest news
G
GRAHAM CLULEY
Blog — PlanetScale
Blog — PlanetScale
J
Java Code Geeks
N
News and Events Feed by Topic
cs.CL updates on arXiv.org
cs.CL updates on arXiv.org
V2EX - 技术
V2EX - 技术
Stack Overflow Blog
Stack Overflow Blog
www.infosecurity-magazine.com
www.infosecurity-magazine.com
L
LINUX DO - 最新话题
博客园 - Franky
P
Proofpoint News Feed
aimingoo的专栏
aimingoo的专栏
博客园 - 司徒正美
P
Proofpoint News Feed
S
Secure Thoughts
Google DeepMind News
Google DeepMind News
Microsoft Security Blog
Microsoft Security Blog
T
The Exploit Database - CXSecurity.com
让小产品的独立变现更简单 - ezindie.com
让小产品的独立变现更简单 - ezindie.com
C
CXSECURITY Database RSS Feed - CXSecurity.com
F
Full Disclosure
Security Latest
Security Latest

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.