Loading…

Joe Steinbring

Personal website and digital garden

jws.dev

A statically generated personal site in English and Spanish: professional experience, résumé, travel history, projects, and public activity, with its own JSON API and self-hosted behind NGINX.

In active development
The jws.dev homepage: a full-length portrait beside an introduction with links to my experience, résumé, and projects.
The jws.dev homepage: a full-length portrait beside an introduction with links to my experience, résumé, and projects.
The jws.dev homepage, captured from the current build.

At a glance

Project statistics

Travel destinations
112
Languages
2
Technologies documented
84
API collections
14
Uptime
100%Over the last 30 days, measured by UptimeRobot.
Days since last change
51
Days since last build
0

Purpose and direction

The goal

jws.dev brings my professional history, projects, travel, and public activity together under one domain I own and host myself. It is deliberately built to be evidence rather than assertion: every page is prerendered and served by NGINX as static files, so Node.js is used only by the build system and never at request time. The whole site is published in English and Spanish, the same content is available through a versioned JSON API so a native app can be built against it, and an automated audit suite has to pass before a build ships.

Changelog

Recent changes

The last few things I have documented about jws.dev.

  1. ChangedLatest

    Build output kept out of the repository

    The static maps, social cards, site metrics and photo snapshots are all rebuilt from scratch on every build, so they no longer belong in version control.

  2. Added

    A preview card for every page

    Every page now previews with a drawn card of its own — a heading, a line of context and either my portrait or the site mark — instead of the bare app icon.

  3. Fixed

    A state map opens on the state

    Opening New Mexico showed the whole of the United States: the map set its zoom floor before framing the shape, leaving the fit with nothing it was allowed to do.

  4. Added

    China, and New Mexico

    Two additions to the travel section: the 2018 trip through Shanghai, Wuxi, Suzhou and Beijing, and a New Mexico page drawn from two family trips to Cimarron and Taos.

All 7 changes

Connected to my experience

Technologies used

Select a technology to read more about my experience with it and how I use it.

Explore the technology stack

AstroStatic routing, generated content pages, metadata, optimized image delivery, and production output served by NGINX.Vue.jsServer-rendered components, and focused interactive islands for the maps, photo carousels, travel explorer, activity feeds, and related writing.Node.jsBuild-time content generators, consistency audits, data processing, and résumé output; Node.js is not required by the production web server.Web componentsStandards-based reusable components that work cleanly alongside Astro and Vue.Web AwesomeLocally installed cards, page layout, observers, controls, and other interface primitives.Font AwesomeLocally served semantic icons for navigation, actions, technology references, and metadata.LeafletInteractive travel maps enhanced with local static fallbacks for offline resilience.Semantic HTMLMeaningful page structure for accessibility, search visibility, and durable no-script content.AccessibilityKeyboard support, reduced-motion behavior, descriptive media, landmarks, and clear status messages.InternationalizationEvery page in English and Spanish from one content source, with per-locale routes and one catalogue of interface strings shared by the Astro pages and the Vue islands.API developmentA versioned JSON API that mirrors the site content, with a manifest of content hashes so a client can tell what changed before downloading anything.Search engine optimizationPer-locale canonicals, reciprocal hreflang between English and Spanish, a generated sitemap, and structured metadata on every route.WebAIM WAVEWebAIM’s WAVE API scores a sample of the published pages; the AIM score below comes from it, with the day it was taken.Hosting & infrastructureSelf-hosted on my own server and served as static files behind NGINX, with no third-party analytics or tracking.SpaceshipThe VPS the site runs on, where NGINX serves the prerendered pages as static files.CloudflareThe reverse proxy in front of the site, between its visitors and the server.GitVersion control for the site’s source and its content, with every change recorded.

Built for a second front end

The JSON API

The same content that renders these pages is published as a versioned JSON API, so a native app could be built against it without scraping the site. Version 1 exposes 14 collections across 2 languages from 15 endpoints.

A client only needs one URL. The manifest lists every collection with a hash of its contents, so an app can compare hashes and tell what changed since its last sync before downloading anything.

Read the manifest

Measured, not asserted

Performance and accessibility

These numbers are generated during the build by measuring the output, so they describe the version of the site you are reading rather than a good day in a testing tool.

Pages prerendered
604Every route is static HTML; nothing is rendered on request.
JavaScript shipped
895 KBAcross the whole site. Interactive islands load only where a page needs them.
CSS shipped
120 KBAcross the whole site, most of it in one stylesheet that every page shares.
Median HTML page
50 KBThe HTML alone, before scripts, styles, and images. The largest is 391 KB.

Accessibility scores

WCAG 2.2 A and AA
301 / 301Pages with no failures, checked by axe-core in a real browser.
AAA colour contrast
301 / 301Pages where all text reaches 7:1, or 4.5:1 when large.
WebAIM AIM score
10.0 / 10Average of 8 pages through the WAVE API, measured October 4, 2026.

axe-core 4.13.0 loaded every English page in headless Chrome on November 11, 2026, maps included, and tested it against WCAG 2.0, 2.1 and 2.2 at levels A and AA, and against the AAA contrast level. The Spanish pages share their templates. No page failed a check. The AIM score, from WebAIM, rates a page from 1 to 10 against a million home pages; 5 is about average.

Accessibility across all 604 pages

  • Declare a document languageevery page
  • Have exactly one top-level headingevery page
  • Expose a main landmarkevery page
  • Describe every imageevery page
  • Avoid positive tab indexesevery page

These are checks a build can make honestly; contrast is measured by the browser run above. Focus order is verified by hand, because it needs a real keyboard to judge.