DEV Community

Naeem Ullah
Naeem Ullah

Posted on

I Built a 44-Page Calculator Site With Next.js ISR — Here's the Architecture That Actually Ranks

Most calculator sites are a graveyard of orphan pages: 50 tools, no structure, everything competing against everything. I rebuilt mine — seecalc.com — around a hub-and-spoke architecture on Next.js with ISR, and it changed how Google treats the entire domain. Here's the full setup.

The problem with flat calculator sites

My first version was flat: /date-calculator, /cd-rate-calculator, /cap-rate-calculator — all siblings, all fighting for crawl budget and authority independently.

Two symptoms showed up in Search Console:

Click concentration. The homepage absorbed most clicks while individual tools sat at positions 10–20 — close enough to taste page one, never getting there.
No topical clustering. Google had no signal that my date tools formed a coherent topic. Each page ranked (or didn't) on its own.

If your internal linking graph is flat, your authority distribution is flat. Spoke pages stuck at position 12 usually don't have a content problem — they have a link-equity problem.

Hub-and-spoke: the structure

The fix is boring and effective:

/date-calculator ← HUB (broad head term)
├── /days-between-dates ← spoke
├── /date-plus-days ← spoke
├── /weeks-between-dates ← spoke
├── /business-days-calculator
└── /age-calculator

Rules I enforce in code, not by hand:

Every spoke links up to its hub in the intro paragraph and breadcrumb.
The hub links down to every spoke with descriptive anchor text (not "click here").
Spokes cross-link laterally only when contextually justified (days-between → business-days makes sense; days-between → cap-rate does not).
No orphans. A page that isn't reachable within 2 clicks from a hub doesn't ship.

I keep the topology in a single config file so links are generated, not remembered:

ts// clusters.ts
export const clusters = {
"date-calculator": {
hub: { slug: "date-calculator", title: "Date Calculator" },
spokes: [
{ slug: "days-between-dates", title: "Days Between Dates" },
{ slug: "date-plus-days", title: "Add Days to a Date" },
{ slug: "business-days-calculator", title: "Business Days Calculator" },
// ...
],
},
// finance cluster, health cluster...
} as const;

Every page component pulls its cluster and renders from this map. Add a spoke to the config → hub updates, siblings update, sitemap updates. Zero manual linking drift.

Why ISR instead of pure SSG or client-only

The calculators themselves are client-side (instant, no server round-trip). But the pages are statically generated with Incremental Static Regeneration:

ts// app/[cluster]/[tool]/page.tsx
export const revalidate = 86400; // rebuild daily

export async function generateStaticParams() {
return Object.values(clusters).flatMap(c =>
[c.hub, ...c.spokes].map(t => ({ tool: t.slug }))
);
}

Reasons this combination wins for SEO tool sites:

Full HTML at crawl time. Googlebot gets rendered content, headings, FAQ markup — not a JS shell. Client-only React tools consistently underperform in my Search Console data.
Content updates without redeploys. I run periodic content-enrichment passes (benchmark tables, sourced data, FAQ expansions). With revalidate, updated content ships on the next request after the window — no CI pipeline run for a copy change.
CWV for free. Static HTML + client hydration only for the calculator widget = green LCP/CLS, which matters more post-INP.

Content enrichment: what moved spokes, what didn't

Structure gets you crawled and clustered. It doesn't get you ranked on its own. I ran an enrichment pass across all 44 pages. What measurably helped:

Sourced benchmark tables. On the CD rate calculator, adding a current-rates comparison table (with cited sources and a last-updated date) beat any amount of prose. Tables win featured snippets; paragraphs don't.
Answering the adjacent question. People on "days between dates" also ask "does it include the end date?" A 2-sentence FAQ with FAQPage schema captured long-tail impressions the tool alone never triggered.
E-E-A-T signals on YMYL-adjacent pages. Financial calculators got methodology notes and formula transparency. Rankings on money pages responded; date pages didn't care.

What didn't help: adding word count for its own sake. A 400-word page that answers the query outranks a 1,500-word page that pads it. I cut copy on several pages during the same pass with no ranking damage.

Results and honest caveats

I won't fabricate a hockey-stick chart. What I can say from Search Console:

Spoke pages that were stuck at positions 10–20 started moving after internal links from an established hub pointed at them — internal linking is the cheapest ranking lever most tool-site builders ignore.
Impressions diversified away from the homepage; the click distribution is less top-heavy than the flat version.
One thing structure can't fix: AI Overviews eating definitional queries. "How many days until X" style informational impressions get answered in-SERP now. Tool intent ("calculate", "calculator") still clicks through. Build for tool intent, not definitions.

TL;DR

Model your site as clusters in config, generate links from it — never hand-maintain internal linking.
ISR + client-side calculator widget = crawlable HTML, instant UX, green CWV.
Enrich spokes with tables, FAQs and schema, not word count.
Internal links from hubs are how you unstick pages at positions 10–20.

The live implementation is at seecalc.com if you want to click through the cluster structure. Happy to answer questions about the Next.js setup or the GSC data in the comments.

Top comments (0)