Tools
Tools: Rebuilding My Static Blog with Build-Time Data and Instant Search
2026-02-04
0 views
admin
The problem with my old setup ## Build-time data as a contract ## Fetching and aggregating blog data ## Hashnode: canonical content ## Dev.to: distribution and extra reach ## Matching articles across platforms ## Writing the static data contract ## Replacing runtime APIs with static services ## Instant search and sorting ## Trade-offs and lessons learned Static sites are supposed to be fast, simple, and reliable. But 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. 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 This had a few 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. The core decision was simple: 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. This one decision simplified everything else. Hashnode remains the source of truth for: Title, slug, content, tags I fetch all posts using Hashnode’s GraphQL API with pagination handled inside a Node.js script. Dev.to is where additional readers come from, so ignoring those views felt wrong. Using the Dev.to API (with a personal access token), I fetch all my articles and extract: This is the tricky part. Articles are matched using a layered strategy: Once matched, the final view count becomes: The output for each blog includes: Platform-specific views (for debugging) Dev.to URL (if matched) All processed data is written to the static/blogs.json file. Generated at build time Treated as read-only by the app It also includes metadata like the last updated time and the total blog count. This JSON file effectively replaces my entire blog API. Previously, services/blogs.js made live GraphQL calls. After the refactor: The service dynamically imports blogs.json find, findOne, and search all operate locally From the UI’s perspective, nothing changed - but under the hood, everything became predictable. Once all blog data is local, search becomes trivial. Client-side text search (title, brief, tags) Because the dataset is small and static: Search results are instant Sorting is deterministic This dramatically improves discoverability without introducing a search service. This approach isn’t perfect: Build time increases slightly The JSON file grows over time It’s not suitable for real-time analytics But for a personal blog, the trade-offs are worth it. The key takeaways from the refactor that made me realize that: 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. Templates let you quickly answer FAQs or store snippets for re-use. Are you sure you want to hide this comment? It will become hidden in your post, but will still be visible via the comment's permalink. Hide child comments as well For further actions, you may consider blocking this person and/or reporting abuse CODE_BLOCK:
Hashnode + Dev.to ↓
Build-time fetch & normalize ↓
static/blogs.json ↓
Nuxt UI (search, sort, views) Enter fullscreen mode Exit fullscreen mode CODE_BLOCK:
Hashnode + Dev.to ↓
Build-time fetch & normalize ↓
static/blogs.json ↓
Nuxt UI (search, sort, views) CODE_BLOCK:
Hashnode + Dev.to ↓
Build-time fetch & normalize ↓
static/blogs.json ↓
Nuxt UI (search, sort, views) CODE_BLOCK:
combinedViews = hashnodeViews + devtoViews Enter fullscreen mode Exit fullscreen mode CODE_BLOCK:
combinedViews = hashnodeViews + devtoViews CODE_BLOCK:
combinedViews = hashnodeViews + devtoViews - 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 - 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 - 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 - Title, slug, content, tags
- Publish date
- Cover image
- Base view count - canonical_url
- page_views_count - Canonical URL match
- Title match - Combined views
- Platform-specific views (for debugging)
- Dev.to URL (if matched) - Generated at build time
- Git-ignored
- Treated as read-only by the app - The service dynamically imports blogs.json
- find, findOne, and search all operate locally
- No pagination state
- No network failures - Client-side text search (title, brief, tags)
- Sorting by: Published date (recent / oldest)
View count (most / least)
- Published date (recent / oldest)
- View count (most / least) - Published date (recent / oldest)
- View count (most / least) - Search results are instant
- No debouncing hacks
- No loading states
- Sorting is deterministic - Build time increases slightly
- The JSON file grows over time
- It’s not suitable for real-time analytics - Static doesn’t mean lifeless
- Build-time data pipelines are underrated
- One clean data contract simplifies UI, UX, and performance
how-totutorialguidedev.toainetworknodegit