Quick answer: If you shuffle a list on every request, every visitor (and every refresh) gets a different order, and your server redoes the work each time. To fix it, cache the page so the shuffle runs once and the same random order is reused. In Next.js, add export const revalidate = 43200 to your page. The shuffle is saved for 12 hours (43,200 seconds), then it reshuffles.
The problem: random order on every request
You have a list of items (say, 20 tools or posts) and you want them in random order so the page feels fresh.
The simple way is to shuffle inside the page:
tsx
// app/explore/page.tsx
import items from '@/data/items.json'
function shuffle<T>(list: T[]): T[] {
const copy = [...list]
for (let i = copy.length - 1; i > 0; i--) {
const j = Math.floor(Math.random() * (i + 1))
;[copy[i], copy[j]] = [copy[j], copy[i]]
}
return copy
}
export default function ExplorePage() {
const list = shuffle(items)
return (
<ul>
{list.map((item) => (
<li key={item.id}>{item.name}</li>
))}
</ul>
)
}Math.random() gives a random number between 0 and 1, and the loop uses it to mix up the list.
What you get depends on how Next.js treats the page. There are three outcomes.
SetupWhen the shuffle runsWhat users seePage is dynamic (rendered per request)On every requestA new order on every refreshPage is static (default, no extra setup)Once, at build timeThe same order forever, until your next deployPage uses ISR with revalidate = 43200Once every 12 hoursThe same order for 12 hours, then a new one
Most people want the third row. Let me explain why the first two are a problem.
Outcome 1: shuffle on every request (what happens)
This happens when the page is dynamic, meaning Next.js renders it fresh for each visitor. A page becomes dynamic when it uses things like cookies(), headers(), searchParams, or export const dynamic = 'force-dynamic'.
What goes wrong:
The list jumps on every refresh. A visitor refreshes and the items they were looking at move. They lose their place.
Two visitors see different pages. That makes bug reports and screenshots hard to match up.
More work for your server. Every visit runs the shuffle and renders the whole page again.
Slower pages. Nothing is saved, so nothing can be served instantly from a cache or CDN (Content Delivery Network, servers around the world that store and serve copies of your pages).
Inconsistent for search engines. A crawler can see a different order each time it visits.
Outcome 2: no revalidate on a static page (what happens)
If your page doesn’t use any dynamic features, Next.js treats it as static. It builds the page once during deploy and saves it.
The shuffle runs once, at build time. After that, everyone sees the exact same order until you deploy again. If you deploy once a month, your “random” list is frozen for a month.
So with no setup you get either “too random” or “never changes”. Neither is what you wanted.
The fix: cache the shuffle for 12 hours
Add one line:
tsx
// app/explore/page.tsx
import items from '@/data/items.json'
export const revalidate = 43200 // 12 hours in seconds
function shuffle<T>(list: T[]): T[] {
const copy = [...list]
for (let i = copy.length - 1; i > 0; i--) {
const j = Math.floor(Math.random() * (i + 1))
;[copy[i], copy[j]] = [copy[j], copy[i]]
}
return copy
}
export default function ExplorePage() {
const list = shuffle(items)
return (
<ul>
{list.map((item) => (
<li key={item.id}>{item.name}</li>
))}
</ul>
)
}revalidate = 43200means “keep this page for 12 hours”. The math is 12 x 60 x 60 = 43,200 seconds. Write it as a plain number, not a calculation.The shuffle runs while the page is being built. The result is saved inside the cached page.
Everyone gets that saved copy, so everyone sees the same random order.
That’s the key idea: the randomness gets frozen inside the cache.
What happens step by step
First build or first visit: Next.js renders the page. The shuffle runs. The result is saved.
Hours 0 to 12: every visitor gets the saved page. The shuffle code does not run again.
After 12 hours: nothing happens on its own. There is no timer or cron job (a scheduled task).
The next visitor after expiry: they still get the old list. Their visit tells Next.js to rebuild the page in the background.
The visitor after that: they get the new shuffle, saved for another 12 hours.
Showing the old copy first while a new one is built in the background is called stale-while-revalidate. Stale just means old.
What if you don’t cache it? (summary)
Dynamic page: random on every refresh, slower, more server work.
Static page: random only once per deploy, then frozen.
With
revalidate: random once per 12 hours, fast, same for everyone.
Common mistakes (and what they cause)
1. Reading cookies(), headers() or searchParams on the page
Next.js stops caching and renders on every request, so your shuffle runs every time. This often hides in a shared component, like a navbar that reads the user session.
2. A parent layout with revalidate = 0
A layout is a wrapper around your pages. If it forces the route to be dynamic, your page setting looks ignored. When caching seems broken, check the layouts.
3. Testing in next dev
Development mode doesn’t cache like production. Test with next build and then next start.
4. Expecting exactly 12 hours
The timer only moves when someone visits. On a quiet site the list can stay old for much longer.
5. Shuffling in the browser
If you shuffle inside a Client Component (a component that runs in the browser), the server and browser can produce different orders and React may show a mismatch warning. Do the shuffle on the server.
Does a deploy reset the list?
Yes. A new deploy builds a new page, so the list reshuffles. If you deploy 5 times a day, it changes 5 times a day.
Multiple servers: will everyone see the same list?
On one server or a managed host, yes. If you host it yourself on several servers, each can keep its own saved copy and show a different shuffle. A seeded shuffle fixes this. A seed is a starting number, and the same seed always gives the same order:
tsx
const WINDOW = 12 * 60 * 60 * 1000 // 12 hours in milliseconds
const bucket = Math.floor(Date.now() / WINDOW) // same number for 12 hours
// pass `bucket` into a seeded shuffle functionEvery server computes the same order for the same 12 hour window. You can then drop Math.random() for a seeded random function.
How to check it’s working
bash
curl -I https://yoursite.com/explore | grep -i x-nextjs-cacheHIT: served from the saved copy (good)MISS: built freshSTALE: served the old copy while rebuilding
If you see MISS every time, something is forcing the page to be dynamic.
When to use this
Explore, Discover or Featured pages that are the same for every visitor
Lists from a JSON file or data that rarely changes
Pages where speed matters more than live accuracy
When NOT to use this
Personalized lists. One saved page can’t be different for each user.
Live data like prices, stock or scores.
Exact schedules like “reshuffle at 9:00 sharp”. Use a real scheduler.
Pages that need cookies or search params to render.
Equal exposure. One frozen order for 12 hours favors whoever lands on top. Use a rotation instead.
Heads up on Next.js 16
If you turn on the newer Cache Components feature, the rules for random values change and you must handle them explicitly. This guide uses the standard revalidate setup. Check the docs for your version.
FAQ
How do I stop a list from reshuffling on every refresh in Next.js?
Cache the page. Add export const revalidate = 43200 so the shuffle runs once and is reused for 12 hours.
Why does my Next.js list reshuffle on every request?
The page is being rendered dynamically. Look for cookies(), headers(), searchParams, force-dynamic, or a layout with revalidate = 0.
Why is my random list never changing?
The page is static, so the shuffle ran once at build time. Add revalidate so it regenerates.
Does revalidate run exactly every 12 hours?
No. It rebuilds on the first visit after the time expires, and that visitor still sees the old list.
Will every user see the same random order?
Yes for one saved copy. Multiple self hosted servers may differ unless you use a seeded shuffle or a shared cache.
What is stale-while-revalidate?
Serving an old copy right away while a fresh one is built in the background.
The simple rule
Random on every request is slow and jumpy. Random once and cached is fast and steady.
If everyone seeing the same list, a little late, is fine for your page, cache it.
Sources: Next.js ISR docs, Next.js caching with Cache Components, App Router caching without the folklore

Comments
No comments yet
Be the first to share what you think.