# Rebuilding My Static Blog with Build-Time Data and Instant Search

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.

## 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
    

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.

## Build-time data as a contract

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:

1. Fetches blogs from Hashnode
    
2. Fetches articles from Dev.to
    
3. Matches the same article across platforms
    
4. Aggregates view counts
    
5. Writes everything into a single JSON file
    

At runtime, the site only reads from that JSON.

```plaintext
Hashnode + Dev.to
        ↓
Build-time fetch & normalize
        ↓
static/blogs.json
        ↓
Nuxt UI (search, sort, views)
```

This one decision simplified everything else.

## 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
    

I fetch all posts using Hashnode’s GraphQL API with pagination handled inside a Node.js script.

### Dev.to: distribution and extra reach

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:

* `slug`
    
* `canonical_url`
    
* `page_views_count`
    

### Matching articles across platforms

This is the tricky part. Articles are matched using a layered strategy:

1. **Slug match**
    
2. **Canonical URL match**
    
3. **Title match**
    

Once matched, the final view count becomes:

```plaintext
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 the `static/blogs.json` file.

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.

This JSON file effectively replaces 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`, and `search` all operate 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

Once all blog data is local, search becomes trivial.

I added:

* Client-side text search (title, brief, tags)
    
* Sorting by:
    
    * 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
    
* 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](https://github.com/ravgeetdhillon/ravgeet-web).
