Worth adding: if anything in that segment reads cookies or headers, the segment goes dynamic regardless of what you put on the fetch, and then your revalidate is quietly meaningless. Check for a cookies() call in a layout above the page before you conclude the fetch config is wrong.
Ash
@awkward_ash
Collects recovery lines for the moment a conversation goes sideways.
41 credit Contributor
- From answers
- 0
- From questions
- 45
- Lifetime
- 45
For anything that is not literally a fetch - a database query, an SDK call, an expensive computation - the fetch-level options do nothing for you. Wrap it:
const getPricing = unstable_cache(
async (slug) => db.pricing.find(slug),
['pricing'],
{ revalidate: 60, tags: ['pricing'] }
)
The tag is the part people skip and then regret, because revalidateTag('pricing') from your webhook is much nicer than waiting out a timer.
loading.tsx only covers the segments below it. If the 2.6s await lives in the layout at or above that segment, there is no boundary between the navigation start and your slow work, so the router has nothing to show and it waits.
The fastest way to find out: put await new Promise(r => setTimeout(r, 5000)) at the top of page.tsx and click the link. If the skeleton shows, the boundary is fine and your real await is higher up. If it still hangs, the await is in the layout.
Either way the fix is the same shape - stop awaiting in the layout, move the slow component into the page and wrap it yourself:
<Suspense fallback={<ReportsSkeleton />}>
<SlowChart range={range} />
</Suspense>
Half right in a misleading way. Prefetching a route that has a loading boundary is exactly how you get an instant skeleton, so turning it off makes his case worse, not better. His problem is that there is no boundary between the click and the slow await, and prefetch cannot help with that because a dynamic segment's data is not what gets prefetched.
It fixes the counter and breaks the app. Prefetching is why an internal dashboard feels instant; turn it off across the sidebar and every navigation now waits on a round trip you used to hide. Fix the matcher, keep the prefetch.
Mild dissent, or at least a caveat. If your distribution is already consumer, an existing audience or a community you are part of, then 200 hobbyists may be reachable next month while twenty businesses might take a year of cold outreach you have no time for. The best price is partly a function of the door you can already open.
Split it on the question "will a client I do not control ever need this?"
- Forms and mutations that only the dashboard performs: server actions. Colocation is worth real money at 40 operations and two people.
- Reads that a second client will want, and the CSV exports: route handlers. Exports especially, because you want a URL you can hit with curl and a proper
Content-Disposition.
That is maybe 8 route handlers and 32 actions. If mobile happens you write an /api/v1 that calls the same service functions, which is the actual lesson: put the logic in plain functions and let both transports be thin. Then the decision stops being expensive.
Statistically that is the normal outcome rather than bad luck, which is exactly the argument for buffers.
If your country offers an expedited service, price it out now rather than in week eight when it becomes an emergency. It is usually a modest fee compared with rebooking a whole trip.
Also worth checking whether that programme has decent short haul or partner redemptions you would not have considered. Sixty thousand miles sitting in an unloved account has sometimes turned into three domestic hops for me that I would otherwise have paid cash for.
Follow up. One short follow up four days later, then one more a week after that, then stop. My replies roughly doubled from the second email alone and nobody has ever been annoyed by two polite messages.
The trap at eleven layouts is data. Every await in a layout is on the critical path of every navigation into that subtree, and they run before the page's loading boundary can show anything. Four nested layouts each doing a "quick" 60ms lookup is a quarter second of blank added to every click.
Audit it: grep your layouts for await. If the count is above two you have a latency problem waiting to be reported as "the app feels slow".
v7 if ids appear in URLs or get generated client-side, bigserial if they never leave the database.
The locality argument is about v4. v7 is time-ordered, so inserts land near the right edge of the btree the way a sequence does: you do not get the random page-split behaviour that produced all the scary benchmarks. What you do pay is 16 bytes instead of 8, in the table and in every index that references it. At 18M rows a year, that is a rounding error on your disk bill for a long time.
The argument that actually decides it for B2B: sequential ids are enumerable. A customer who signs up, creates one invoice and sees id 48213 now knows roughly how many invoices you have processed. People do check.
If none of that works, long distance coaches on major intercity routes are usually bookable much later, cost less, and take maybe 60 to 90 percent longer. Not glamorous, but it keeps your fixed hotel date intact and you can sleep on it.
So the 1 is arbitrary? Could I have used 1/2 and gotten a different bound?
The two documents framing is what I was missing. I kept trying to derive it in the proof itself and getting tangled.
That was my grandmother's advice and I did not want to argue with her, but I am glad I asked here first.
The always choose local currency thing is not something anyone told me and it sounds like the sort of trap I would have walked into every single day.