Rebuilding My Static Blog with Build-Time Data and Instant Search
Source: Dev.to

Static sites are supposed to be fast, simple, and reliable. Over time, my personal blog started behaving like a dynamic app—runtime API calls, pagination logic everywhere, and fragmented view counts spread across platforms.
Last week, I rebuilt the blog section of ravgeet.in (Nuxt.js) to fix this properly. The end result is still a static site, but now it feels alive: aggregated view counts, instant search and sorting, and zero runtime dependencies on external APIs.
This post walks through the thinking, architecture, and trade‑offs behind that rebuild.
The problem with my old setup
Originally, my blog worked like this:
- Blog content lived on Hashnode (canonical source)
- Some posts were also cross‑posted to Dev.to
- Pages fetched blog data at runtime using Hashnode’s GraphQL API
- Pagination logic (
hasNextPage, cursors) lived inside the UI
Downsides
- A static site depending on live APIs felt wrong
- Local development and builds were slower and flaky
- Adding features like search or sorting would require more APIs
I wanted the blog to stay static—but smarter.
Build‑time data as a contract
Move all external data fetching to build time, and treat the result as immutable static data.
Instead of fetching blogs at runtime, I introduced a build step that:
- Fetches blogs from Hashnode
- Fetches articles from Dev.to
- Matches the same article across platforms
- Aggregates view counts
- Writes everything into a single JSON file
At runtime, the site only reads from that JSON.
Hashnode + Dev.to
↓
Build‑time fetch & normalize
↓
static/blogs.json
↓
Nuxt UI (search, sort, views)
Fetching and aggregating blog data
Hashnode: canonical content
Hashnode remains the source of truth for:
- Title, slug, content, tags
- Publish date
- Cover image
- Base view count
All posts are fetched using Hashnode’s GraphQL API with pagination handled inside a Node.js script.
Dev.to: distribution and extra reach
Dev.to brings additional readers, so ignoring those views felt wrong. Using the Dev.to API (with a personal access token), I fetch all my articles and extract:
slugcanonical_urlpage_views_count
Matching articles across platforms
Articles are matched using a layered strategy:
- Slug match
- Canonical URL match
- Title match
Once matched, the final view count becomes:
combinedViews = hashnodeViews + devtoViews;
The output for each blog includes:
- Combined views
- Platform‑specific views (for debugging)
- Dev.to URL (if matched)
Writing the static data contract
All processed data is written to static/blogs.json. This file is:
- Generated at build time
- Git‑ignored
- Treated as read‑only by the app
It also includes metadata like the last updated time and the total blog count, effectively replacing my entire blog API.
Replacing runtime APIs with static services
Previously, services/blogs.js made live GraphQL calls. After the refactor:
- The service dynamically imports
blogs.json find,findOne, andsearchoperate locally- No Axios, no pagination state, no network failures
From the UI’s perspective, nothing changed—but under the hood, everything became predictable.
Instant search and sorting
With all blog data local, search becomes trivial. I added:
- Client‑side text search (title, brief, tags)
Sorting options
- Published date (recent / oldest)
- View count (most / least)
Because the dataset is small and static:
- Search results are instant
- No debouncing hacks
- No loading states
- Sorting is deterministic
This dramatically improves discoverability without introducing a search service.
Trade‑offs and lessons learned
This approach isn’t perfect:
- Build time increases slightly
- The JSON file grows over time
- Not suitable for real‑time analytics
But for a personal blog, the trade‑offs are worth it. Key takeaways
- Static doesn’t mean lifeless
- Build‑time data pipelines are underrated
- One clean data contract simplifies UI, UX, and performance
If you’re curious, the full implementation lives in the ravgeet.in repository.