Skip to main content
ADR-001accepted

Static Site Generation over Server-Side Rendering

Context

The portfolio site serves content that changes infrequently — work experience, education records, technology stack, and biographical data. The API backing these pages (api.lucioduran.com) returns stable payloads that update at most a few times per month. Server-Side Rendering (SSR) via getServerSideProps would require a Node.js runtime on every request, introducing per-request latency proportional to the API response time plus server-side React rendering. For a site with zero authenticated state and no user-specific content, SSR introduces computational overhead with no functional benefit. The critical performance metric here is Time to First Byte (TTFB), which directly impacts Core Web Vitals and perceived load speed.

Decision

Adopt Static Site Generation (SSG) using Next.js getStaticProps as the default data-fetching strategy for all content pages. HTML is pre-rendered at build time, producing static artifacts that are deployed to Vercel's Edge Network. The build process fetches all data from the API once, serializes it into the page props, and outputs fully hydrated HTML files. Pages that require real-time data — specifically the curated feed and news sections — use getServerSideProps as a deliberate exception. This creates a clear architectural boundary: static pages are CDN-cached globally with sub-50ms TTFB, while dynamic pages trade latency for freshness.

Consequences

Positive: TTFB dropped from ~400ms (SSR, origin in South America) to ~18ms (CDN edge). Lighthouse performance scores consistently hit 95-100. Zero runtime compute cost for static pages — Vercel's free tier covers the entire site. Build-time failures surface data issues before deployment rather than at request time. Negative: Content updates require a rebuild and redeploy, introducing a ~90-second propagation delay. The API must be available during build time, creating a hard dependency on infrastructure availability at deploy. Any structural change to the API response format breaks the build rather than degrading gracefully at runtime. This trade-off is acceptable: the site prioritizes delivery performance over content freshness, and the API is under direct control.

Calibrated Uncertainty

Predictions at Decision Time

Expected SSG to deliver sub-50ms TTFB globally, eliminate runtime compute costs, and provide Lighthouse scores consistently above 90. Predicted the 90-second redeploy cycle would be acceptable given infrequent content updates (estimated 2-3 times per month). Assumed build-time API dependency would not become a reliability bottleneck because the API is under direct operational control.

Measured Outcomes

TTFB measured at ~18ms from CDN edge — significantly better than the sub-50ms target. Lighthouse scores hit 95-100 consistently, exceeding the >90 prediction. The redeploy cycle is tolerable but more friction than expected: during active development phases, content changes sometimes cluster (5-10 deploys in a single day), making the 90-second wait noticeable. The API availability assumption held — zero build failures attributable to API downtime in 20+ months of production. The unexpected win: zero-cost hosting on Vercel's free tier, which was hoped for but not guaranteed at the traffic levels observed.

Unknowns at Decision Time

Did not anticipate how frequently the site would be rebuilt during active development versus steady-state operation. The mental model was 'update content monthly, deploy once' — but the reality is rapid iteration on layout, styling, and new sections triggers dozens of deploys per week during active phases. Also unknown at decision time: whether Vercel would change free tier limits for SSG sites, and whether Next.js 15 would introduce any breaking changes to getStaticProps behavior. Neither materialized as issues.

Reversibility Classification

Two-Way Door

Switching from SSG to SSR requires changing getStaticProps to getServerSideProps per page — a mechanical refactor with zero data model changes. The same API calls work identically in both modes. Estimated reversal effort: 2-4 hours for all content pages. The only sunk cost is the CDN caching optimization work (cache headers, edge config) which becomes unnecessary under SSR.

Strongest Counter-Argument

The strongest case for SSR was real-time content freshness — particularly for the curated feed and news sections, which ended up using SSR anyway. An SSR-by-default architecture would have eliminated the need for the SSG/SSR bifurcation and simplified the mental model. The counter-counter: SSR would have introduced ~$5-15/month in serverless function costs and added a cold-start latency dimension that SSG completely avoids.

Technical Context

Stack
Next.js 15Vercel Edge NetworkgetStaticProps
Ttfb Before
~400ms (SSR from origin)
Ttfb After
~18ms (CDN edge)
Build Time
~90 seconds
Lighthouse Performance
95-100
Constraints
  • API must be available at build time
  • Content updates require redeploy

Related Decisions