The old version of this post read like a general checklist for choosing Static Generation or Server-side Rendering. The current repository makes the decision more concrete. This site has two different kinds of information: checked-in content that changes with a deployment, and provider data that can change while the deployment stays the same.

The implementation does not force both kinds through one rendering strategy.

The blog belongs to the build

Posts are Markdown files in the repository. Their dates, titles, descriptions, tags, and bodies are known before a request arrives. pages/blog.js therefore uses getStaticProps, while pages/posts/[id].js combines getStaticPaths and getStaticProps.

The result is straightforward: publishing or editing a post requires a new build, and readers receive the generated article rather than waiting for Markdown parsing. The pre-rendering post walks through the gray-matter and remark pipeline in detail.

Static generation also gives the post system one consistent metadata source. The same frontmatter used by the blog index supplies post SEO, RSS descriptions, sitemap dates, reading-time displays, and navigation between articles. Preserving the original date on a rewritten article is important because that date flows into more than the visible byline.

Dynamic does not automatically mean SSR

GitHub stars, Spotify playback, and Steam activity are different. Their values can change without a source-code change, and some requests may require server-side credentials. This site handles those values with Pages Router API routes plus client-side SWR, not by server-rendering the full page on every visit.

For example, the dashboard component asks for an aggregate endpoint:

const { data, error, mutate } = useSWR('/api/dashboard', fetcher, {
  refreshInterval: 60000,
  dedupingInterval: 30000,
})

The initial page structure can render without those metrics. SWR requests /api/dashboard, shows loading or error states as needed, and refreshes that aggregate response every 60 seconds with a 30-second deduplication window. The route gathers site metrics plus GitHub, Spotify, and Steam results on the server. It returns source timestamps and statuses so the interface can show whether each integration is fresh or unavailable.

The now-playing component independently requests /api/now-playing with the same 60-second refresh and 30-second deduplication values. The activity timeline is less aggressive: it refreshes every 120 seconds and deduplicates for 60 seconds. On the projects page, /api/project-repos has a 300,000-millisecond SWR deduplication interval and no polling interval.

Those intervals come from the components, not from an assumed site-wide rule. The global SWR configuration supplies a fetcher, disables focus revalidation, and sets a default 60-second deduplication interval, but individual consumers override it where their data has a different cadence.

Server routes add another cache boundary

Client revalidation is only half of the design. The API handlers set shared-cache directives before returning JSON. /api/dashboard uses:

res.setHeader(
  'Cache-Control',
  'public, s-maxage=60, stale-while-revalidate=30'
)

/api/metrics uses a 600-second shared maximum age with 300 seconds of stale-while-revalidate. /api/project-repos uses 900 seconds with a 3,600-second stale window. The activity endpoint uses 120 and 600 seconds respectively.

This means a client refresh does not necessarily become a new request to every upstream provider. SWR controls browser behavior, while response headers give the deployment platform an opportunity to reuse and revalidate server responses. The two layers solve related but different problems.

Where request-time rendering actually appears

An API route executing on the server is not the same as a page using getServerSideProps. The visible dashboard and projects pages do not use getServerSideProps; they render their shells and let SWR load changing data.

There are request-time Pages Router responses in the repository, but they are specialized XML endpoints. pages/sitemap.xml.js and pages/rss.xml.js use getServerSideProps to write XML to the response and apply one-hour shared caching with a one-day stale window. That is a narrower use of request-time work than server-rendering every interactive page.

The practical rule in this codebase is therefore specific: generate repository-owned articles at build time, fetch mutable provider data behind API routes, and reserve request-time page handlers for outputs that need to be produced as responses. See the live result on the dashboard, or read the architecture overview for how these pieces fit together.