Andy FitzgeraldInformation Architect

Perspective

Site Updates: Structure, Semantics & Standards

Greyscale photography of concrete blocks
Photo by uve sanchez on Unsplash

Last week I cut over to a new version of my consulting website. This iteration includes a new short-form "Notes" content type, a richer approach to publishing presentations, and improvements to topical navigation and search. It also includes structural and design updates that make it the most accessible version of the site I've ever built.

It's also the first version for which I entrusted the authoring of my site's Typescript almost entirely to an LLM. I defined and articulated decisions about the content model, component boundaries, URLs, semantics, and accessibility, and then allowed the LLM to build, test, and iterate the templates and integration scripts that instantiate those choices in code. As Claude Code framed it, "I owned the seams, and I delegated the surfaces."

As you would expect, this division of labor allowed me to get more done in less time. I can write Typescript, but I'm abysmally slow at it and tend to get mired in Wagnerian struggles with the type-checker. I also don't enjoy it. Delegating that work to an LLM was a gift.

The time savings, however, wasn't the real benefit. Delegating "surface" work to an LLM significantly changed the quality of the final output I serve to site visitors. The time I might have otherwise spent debugging scripts or puzzling out server configs was now time I could use to explore navigation models, refine search indexing, interconnect content, and expand my coverage of accessibility and linked data affordances.

Looking over this project in retrospect, I can identify four reasons the "seams and surfaces" division led to a higher quality output than I would have otherwise achieved. Here's a brief look at each of them.

Structured Content

My site has used structured content, built and served with Sanity, since 2022. Since its first iteration, my content model has been structured around the formal characteristics of each content type, as opposed to the page templates on which content appears. This deliberately decouples the way content is structured from the way it is presented on any given page, which in turn shifts the process of adding new types from one of "rebuilding" the model to one of "recomposing" elements already in place.

As an example, on the previous version of my site I listed all of my past talks by venue and date on a single page. To get details on the topic of a talk, a visitor had to click through to the venue link and read the talk description (assuming the conference site was still live, of course). For the redesign I added descriptions, slide decks and, where available, recordings and transcripts to each presentation. I also gave each presentation a detail page of its own.

Two versions of the Talks/Presentations page, with the earlier version listing only talk locations and dates, while the updated version includes images, descriptions, decks, and more.
Talks listed on the 2024 version of the site and the more detail rich display of Presentations on the 2026 redesign.

While I did have to create a new "Presentation" type to record new information, the "Event" type that was driving the previous view needed only minor modifications to accommodate new fields. Instead of being the source of the listing (as on the old page), this type was recomposed as a dependent part of the new Presentation type.

By defining this new "seam" of the revised content model up front, the LLM building the templates for these new pages already had all the typed data that appears on them, structured to reflect the conceptual shift in how I had remodeled that content. Using typescript as a translation layer from schema design to front-end implementation gave the LLM a machine-readable specification for mapping my Figma design into composable templates (the "surface").

There was still iteration and fine tuning in the template creation process, but a clear and intentionally structured content schema gave the model far less to guess at or get wrong. This meant I could spend my time reconciling the gaps between content schema and UI design and focus on building a more harmonious system overall.

Semantics

The formal characteristics of a content type are useful for understanding how to structure and articulate a content model. They do nothing, however, to describe what a given piece of content is about, what it means. That's the role of semantics. For the last few years I've managed the semantic relationships between content instances with standards-based taxonomies, built and governed in Sanity using the Taxonomy Manger plugin (which I wrote and maintain 😉).

A view of a plugin operating in the Sanity platform, displaying a hierarchical list of terms, and detail page of an individual term.
The Taxonomy Manager plugin running in Sanity Studio.

Managing content semantics and content structure separately allows each of these elements to co-evolve with minimal disruption to the other — and provides a clear seam that helps an LLM not muddle the two.

For example, I've long wanted to include faceted filtering on my "Insights" page. With a seam that clearly distinguished content semantics from content structure, I was able specify the pattern and logic that afford that functionality in Figma, then let the LLM find the best way to code that into a reusable component.

The Insights and Presentations pages from the redesigned site, showing similar facet filter options.

Since the semantic layer is independent of content types, the pattern worked equally well for the Presentations page, which had also gotten more complex with the redesign.

The separation of content semantics from content structure likewise meant that the seam defined by faceted filtering applied equally well to the Search Results page, which combines all types and genres across both top level nav categories.

The search results page from the redesigned website, showing a similar set of facet filters to the Insights and Presentations pages.

In each of these cases, the separation of structure from semantics allowed me to more easily integrate the new and revised types into the established system of meaning and create a clear seam to pass the the LLM for collaboration on the surface work.

Standards

The taxonomies described above are structured in alignment with the Simple Knowledge Organization System (SKOS) standard, the W3C recommendation for taxonomies, thesauri, and other kinds of controlled vocabularies. Standards-based vocabularies are interoperable with other semantic web technologies by design, which means that extending them to new types and technologies is accounted for from the start. Since these standards are well-established and open, it also means that LLMs already know how to interpret and build with them.

Established and open standards were likewise an important "seam" in ensring accessibility across the new site. Well-structured, standards-compliant semantic HTML already does most of the heavy lifting of providing accessible content to visitors using assistive technologies. As with SKOS, LLMs already have broad context on implementing standards-compliant HTML and using ARIA attributes to fill in the inevitable gaps that remain.

By specifying a seam for template design that prioritized standard markup, the LLM was able to use the context built into its training corpus to recommend semantic and standards-compliant components, and even pushed back on ideas that could have created accessibility dead zones, keyboard traps, or overly verbose screen reader descriptions.

Compared to the previous site, Lighthouse accessibility scores improved from a range of between 84 and 96 to 100 across all pages, and WAVE and manual browser testing indicate a broad improvement in structural elements, use of ARIA roles and properties, and clear navigation affordances.

Delegate and Validate

As this point, it's fair to ask: "But ... is the generated code any good?" As is common with LLM-driven development, it created more code (nearly 3x more) than I had in the previous version of the site. This iteration does, however, use a different static site generator (Astro versus 11ty, my decision), includes enhanced UI features, live previews, and CI testing for accessibility, link integrity, and performance budgets. Some growth in the codebase isn't a huge surprise, but the increase is non-trivial.

The average page weight the site delivers to site visitors, on the other hand, is less than 20% of the previous load (from 1,817 KB to 222 KB median first load). It is also more accessible than the previous version and uses several hundred megabytes less space on my server (from 630 MB to 94MB).

Could the code be less verbose than it is? Perhaps. More importantly, though, I can account for and explain the purpose of every script and component, and I can map each to a design intent. To me this clarity warrants the extra weight in the repository. It reassures me that I haven't (knowingly) included any "black boxes" that may or may not turn out to be bombs later.

There are times I enjoy writing code by hand (not Typescript, of course). I've missed that a bit on this project. But choosing to delegate to the LLM the tasks the LLM is good at has allowed me to spend more time on the tasks that I'm good at, and that has been surprisingly uplifting. It's also helped me build in a way that's different than I would have done on my own. Not just faster, but clearer, lighter, and more accessible for the folks who visit my site. It's helped me build the kind of website I actually want to use, and of that, I'm a fan.

Follow along with RSS

Want to get updates when I publish posts like this in the future? Subscribe via RSS to get new articles and notes — without all the extra noise of social media feeds.

Subscribe via RSS