Refinements

  • Caching the scoped SVG output

    Once the SVG TagHelper produces deterministic scoped output, every render of the same media item is byte-identical — which is a rather lovely property to have, because it makes the whole pipeline cacheable. We can do the read + sanitise + parse + selector-prefix work once and then happily reuse the result for an hour. On cloud-hosted media in particular this is the difference between every page render making N media-storage round-trips and making zero of them, which is a meaningful difference in latency for the end user.

  • Output cache policies for slow upstream APIs

    When your site's performance is held hostage by a slow or rate-limited third-party API (Sessionize, GitHub, anything off-prem), aggressive caching is usually the answer — and ASP.NET Core's [OutputCache] with named policies is the right tool for it. This refinement will walk through the OutputCachePolicies class in this repo, when [OutputCache] beats ResponseCaching (it survives across instances, you control the key, you can vary by query), what cache-key shapes make sense for tenant-scoped data, and how to fail gracefully when the upstream is rate-limited or 500s.

  • Per-tenant 404 pages with a custom INotFoundPageResolver

    Multi-tenant Umbraco sites need multi-tenant 404 pages — it stands to reason that if a request lands on Tenant A's domain and ends up at a missing URL, the user shouldn't suddenly be looking at Tenant B's header and footer just because we couldn't find the page they asked for. This tutorial walks through the small resolver that makes that work, and the subtlety that forces it to resolve the tenant from the domain binding rather than from the (non-existent) current page.

  • Scoping inline SVG <style> to prevent class-name bleed

    There's a surprisingly satisfying class of "the logo went the wrong colour" bugs that all trace back to the same root cause: inline <style> blocks inside SVGs aren't actually scoped to the SVG they live in, even though every bit of your experience tells you they ought to be. This tutorial walks through how we ran into the problem on the Umbraco Community site, why the obvious fixes don't quite hold up, and how we ended up solving it with a small change to a single TagHelper.

  • Syncing custom backoffice configuration across environments

    You've built a feature whose configuration lives in your own table — for us, block-restriction rules keyed by document type. It works beautifully on your machine. Then someone asks the obvious question: a rule you set up locally needs to be on staging and production too, and ideally it should be reviewable in a pull request like the rest of the codebase. Suddenly "it's in the database" is a problem, not a feature. This tutorial is how Block Restrictions makes its configuration travel between environments and live in git.

  • Tenant-aware fallback for schema and SEO metadata

    Structured data — the small blocks of Schema.org JSON-LD that crawlers like Google and Bing look for in your page head — needs a publisher: an Organization with a name, a URL, and a logo, that they can attribute the content to. On a multi-tenant Umbraco site, that publisher is naturally per tenant — Site A's publisher is the Umbraco Community brand, Site B's is the events microsite, and so on. The challenge is that tenant brand metadata is editor-configurable, which means (let us be honest with each other here) that it's also editor-forgettable. This tutorial walks through the small pattern that produces valid Organization schema whether the tenant's brand fields are filled in, partially filled in, or entirely absent.

  • Wrapping Umbraco's native block editor with restriction filtering

    The Block List and Block Grid editors are two of the most useful things Umbraco ships, and by default they offer every block a data type is configured with to every editor, on every node. We wanted to narrow that list per document type (with inheritance down the content tree) — a "Blog Post" should only offer a handful of blocks, a "Landing Page" the full set. This tutorial is about the backoffice side of that: how to enforce the restriction in the editing UI without reimplementing the block editor, by wrapping the native one instead of replacing it. The sting in the tail is copy-and-paste, which breaks in a genuinely puzzling way once you wrap, and needs a little boilerplate to put right.