Colorado Springs · serving the U.S. & Canada Custom-coded. Custom-cared-for.
Appearance
Start Now Or Start a Conversation

The stack, the pipeline, and the reasoning.

This page is written for developers, agency evaluators, and the technically minded business owners who want to verify I know what I am doing before they hire me. There is no hand-waving here, only the actual tools, targets, and measurements behind the work. You can view source on every page on the site if you want to confirm any of it.

The stack

Every site I ship runs on the same foundation. I pick tools that are fast, maintainable, boring in the best sense, and that do not require me to babysit a runtime. Versions and lockfiles are pinned in every repo; you can audit exactly what's running by reading package.json.

Core

  • Eleventy (11ty) v3 ESM: the static-site generator. Converts Nunjucks templates, Markdown content, and JavaScript data files into flat HTML at build time. The config (eleventy.config.js) is ESM with "type": "module" in package.json.
  • Nunjucks: templating. Supports inheritance, partials, filters, and JavaScript data. I compose layouts via extends and partials via include; no monolithic templates.
  • Vanilla CSS with custom properties, clamp(), @container queries where appropriate, and zero framework. One stylesheet, concatenated at build time from organized partials. No @import in production (it causes request waterfalls).
  • Vanilla JavaScript: no framework, no build step for JS, no hydration. Progressive enhancement only. The site works fully without JS; JS exists to make a few interactions nicer.
  • Markdown for blog posts and long-form content via 11ty's built-in markdown-it pipeline. Front-matter for metadata. Content is plain text in .md files; nothing is locked in a database.

Build-time plugins

  • @11ty/eleventy-img: generates AVIF, WebP, and JPEG variants with width-based srcset, native lazy loading, and dimension hints to prevent CLS.
  • @11ty/eleventy-plugin-rss: RSS / Atom feed generation for the blog.
  • @sardine/eleventy-plugin-tinyhtml: HTML minification with conservative whitespace handling.
  • eleventy-plugin-gen-favicons: full favicon set (every device, every browser, every dark/light variant) generated from a single source.
  • eleventy-auto-cache-buster: appends content-hashed query strings to CSS/JS references so returning visitors get fresh assets without serving stale ones.
  • Custom CSS-concatenation extension: registers .css as a template format in eleventy.config.js, reads partial files in declared order, and emits one minified stylesheet. I never ship @import to the browser.
  • Custom sitemap template: sitemap.njk iterates collections.all, filters out assets and excluded pages, and emits a valid sitemap.xml with priority and changefreq. I do not use @quasibit/eleventy-plugin-sitemap or @11ty/eleventy-plugin-sitemap; both have reliability issues with v3.

Functional services (runtime, third-party)

  • Pagefind: static, client-side full-text search. Indexed at build time, served as static JSON chunks, no server required. Runs in an eleventy.after hook so the Cloudflare Pages preset works out of the box.
  • Web3Forms: static-site-friendly form backend. Submissions POST directly to their API; I never touch a server-side form handler. Honeypot + rate-limiting; no CAPTCHA.
  • Tippy.js + Popper: accessible tooltip library for glossary terms. Initialized site-wide; auto-attaches to [data-term], [data-tooltip], and any link to the glossary so authors can write a normal anchor and get a definition tooltip free.
  • Leaflet + OpenStreetMap: service-area maps on /areas/<slug>/ pages and demo sites. No Google Maps tracker, no API key, no cookies. Tiles served directly from OSM. ~200 city coordinates curated in _data/cityCoords.js for nearby-community pin plotting; ambiguous names (Aurora CO/IL, Smyrna GA/TN) are state-scoped.
  • instant.page: prefetches links on hover so navigation feels instantaneous. 1.5 KB, no API.

Analytics

  • Umami: open-source, cookie-free analytics. Primary traffic data. Included with every monthly plan; I host the dashboard and send each client a private live link on launch day.
  • Cloudflare Web Analytics: server-side Real User Monitoring (RUM) for Core Web Vitals. Zero client-side tracking, no cookies, GDPR-compliant by design.

Hosting & DNS

  • Cloudflare Pages: static hosting with global Anycast CDN, automatic HTTPS via free SSL, HTTP/3 by default, unlimited bandwidth on the Free tier. Build container runs npx @11ty/eleventy on every push to main.
  • Cloudflare Workers (when needed): serverless edge functions for routing or simple APIs. I use them sparingly: only when Web3Forms can't cover a need.
  • DNS: Cloudflare nameservers for clients I host fully; for clients who prefer to self-manage at their existing registrar, I provide the exact records to add.
  • Your domain stays in your name. I never own a client's domain. I do not register domains on a client's behalf because it creates the wrong control structure on the wrong day.

Source control & deploy

  • GitHub: every client site lives in a private repo. Commit history available to the client on request. I sign commits with verified GPG keys.
  • CI/CD: Cloudflare Pages' built-in build preset runs npx @11ty/eleventy on every push to main. Preview deploys for every non-main branch. Roll-backs in one click. I do not maintain a separate CI server.
  • Branch protection: main is protected; merges require status checks (build + Lighthouse CI on critical templates).

Interactive tooling I have shipped on this site

The agency's own site doubles as a portfolio of small interactive surfaces. Each is fully client-side: nothing leaves the browser, nothing depends on a backend, all of it works on a static-site host.

  • The lead-leakage calculator. Six inputs (visits, mobile share, current PageSpeed, conversion rate, average job value, close rate), four outputs (annual revenue at risk, lost mobile visits/month, additional leads/year, additional jobs). Pure vanilla JS, runs entirely in the browser, no submission anywhere. Pairs with the long-form math blog post.
  • The free 5-point audit form. Real Lighthouse + schema + conversion-flow review delivered on a 5-business-day SLA. Web3Forms backend, unique subject line for routing.
  • The 8-page comparison surface. Honest head-to-head vs Wix, Squarespace, GoDaddy, Webflow, WordPress, AI builders, traditional agencies, and freelance developers. Each page leads with the lead-generation gap (page speed, schema, form intelligence) instead of the price line, since the agency is not the cheapest option but is the highest-leverage option for service businesses.
  • The walk-through page. Sales-call-free evaluation surface for visitors who want to see the work before booking a call.
  • The redesigned client portal. Single-page dashboard for active clients with quick-action shortcuts, stage-aware feature cards, integrated direct-contact panel, and clean self-serve resource list.

Why static over dynamic

For the kind of sites I build (small-business marketing, service-area pages, blogs), there's no technical reason to be running a database, a PHP runtime, or a Node server on the critical path of a page request. Every layer of runtime infrastructure I remove is a layer that can't break, can't be hacked, and can't introduce latency.

WordPress

WordPress powers ~40% of the web, runs on PHP + MySQL, and is the single most-attacked web platform by a large margin. Every plugin adds attack surface. Every missed update is a vulnerability. Core Web Vitals are a constant fight because of bloated themes, render-blocking scripts, and unoptimized database queries. Even well-tuned WordPress sites struggle to hit the Good threshold without aggressive caching plugins and a page builder that undoes half the optimization.

I do not build on WordPress because the upside (a familiar admin panel) doesn't outweigh the downsides (ongoing maintenance, recurring vulnerabilities, performance ceilings, runtime dependence on a PHP host). Clients do not miss the admin panel once they realize they can just email me what they want changed.

Next.js, Astro, SvelteKit, Remix

Modern meta-frameworks are excellent tools for application-shaped problems: apps with user accounts, real-time state, interactive dashboards, e-commerce with live inventory. For a six-to-twelve-page service-business website they are overkill. They add a JavaScript runtime that the visitor never sees the benefit of, they shift complexity onto the developer (and therefore onto the recurring bill), and they require a Node server or a serverless platform on the hot path, which introduces cold starts, cost variability, and another thing to monitor.

Astro is the closest analogue to what I would reach for if 11ty didn't exist, and its island architecture is elegant. I stay on 11ty because the sites I build don't need islands; they need clean HTML and CSS served from a CDN with sub-100ms TTFB, and 11ty ships exactly that with less abstraction.

Squarespace, Wix, Webflow, Framer

Hosted builders are great for absolute beginners and for businesses that want a website but never plan to grow one. The tradeoff is you are locked into a proprietary platform, you pay $16 to $50 per month forever just to keep the lights on, and your SEO is capped by the platform's decisions. Switching away typically means rebuilding. I would rather my clients own the site outright (or lease it from me with an always-available buyout) than build on rented infrastructure.

Performance targets & methods

I target the Good threshold on every Core Web Vital for both mobile and desktop, on every page of every site I ship. Specifically:

  • Largest Contentful Paint (LCP): < 1.0s (Google's Good threshold is < 2.5s).
  • Cumulative Layout Shift (CLS): < 0.05 (Good: < 0.1).
  • Interaction to Next Paint (INP): < 100ms (Good: < 200ms).
  • Total Blocking Time (TBT): < 100ms.
  • First Contentful Paint (FCP): < 800ms.
  • Time to First Byte (TTFB): < 100ms from any North-American visitor.
  • Google PageSpeed score: 95 to 100 on mobile and desktop.

Performance budget

I enforce a per-page asset budget. If a deploy would exceed it, the build fails until I either trim or justify.

  • HTML: under 25 KB gzipped per page.
  • CSS: one stylesheet, under 30 KB gzipped, shared across the entire site.
  • JavaScript: under 10 KB gzipped per page (typical pages ship 4 to 7 KB).
  • Hero image: under 80 KB AVIF / 120 KB WebP at the responsive width that visitor needs.
  • Web fonts: at most two faces total; subset to Latin only unless content requires more.
  • Third-party scripts: zero by default. Each addition requires a documented reason and a budget line.

How I hit those numbers

  • Zero render-blocking JavaScript. All scripts use defer or are type="module". I ship under 10 KB of gzipped JS on a typical page.
  • Responsive images with AVIF + WebP fallback. Every image has explicit width and height attributes to prevent layout shift, native loading="lazy" for below-the-fold content, and decoding="async" for progressive rendering.
  • System font fallback stack. Bunny Fonts preconnect + font-display: swap means the page renders in a system font if custom fonts take >100ms, eliminating flash of invisible text.
  • CSS concatenated at build. One stylesheet, one HTTP request. No @import cascading requests. Inline cache-busting via query string based on content hash (?v=4a701c154dc9).
  • Minified HTML, CSS, JS. Production builds run through html-minifier-terser with minifyCSS and minifyJS enabled.
  • Cloudflare's edge. Static files served from the CDN closest to the visitor. HTTP/3, Brotli compression, and Early Hints where supported.
  • No web fonts on first-line content. The hero uses system fonts as a fallback so the first paint always has readable text.
  • Preconnect to font origin. A single preconnect hint to fonts.bunny.net in <head> shaves ~200 ms off font-fetch on cold loads.
  • Per-page critical CSS. Because the stylesheet is small enough to ship in full (under 30 KB gzipped), I do not bother with critical CSS extraction. The whole sheet arrives before render.

Image pipeline

Images are the single largest performance lever on a typical small-business site. The pipeline is non-negotiable.

  • Source format: I accept anything the client sends (HEIC, RAW, JPEG, PNG). The build pipeline normalizes to JPEG internally and emits AVIF + WebP + JPEG.
  • Output formats: AVIF first, WebP fallback, JPEG floor. <picture> with <source type="image/avif"> and <source type="image/webp">, <img> as the JPEG floor.
  • Responsive widths: 320, 480, 640, 800, 1200, 1600, 2000 px. Each image gets a srcset with at least four widths; the browser picks the right one based on viewport, DPR, and bandwidth hints.
  • Layout stability: every <img> has explicit width and height attributes (the source dimensions). Browsers use these to reserve space, eliminating layout shift.
  • Lazy loading: loading="lazy" on all below-the-fold images, decoding="async" on everything. The hero image is eager and gets fetchpriority="high".
  • Quality targets: AVIF at q=70, WebP at q=80, JPEG at q=82. These thresholds were tuned by side-by-side comparison; below them, banding becomes visible on photographs.
  • Naming: filenames are content-hashed so cache busting is automatic. A given source produces deterministic output names; the same source compiled twice produces the same files.
  • Total reduction: a 4 MB raw photo from a phone typically becomes a 60 KB AVIF / 90 KB WebP / 140 KB JPEG at the largest delivered size, with smaller siblings down to ~12 KB for mobile.

Font strategy

Web fonts are a performance trap. The default behavior (FOIT or FOUT) is bad enough that I treat font loading as a first-class problem.

  • Provider: Bunny Fonts. GDPR-compliant Google Fonts mirror, no cookies, no tracking, no consent banner required.
  • Preconnect: <link rel="preconnect" href="https://fonts.bunny.net"> in <head>. This opens the connection before the CSS asks for the fonts.
  • Display strategy: font-display: swap. The browser renders text in the system fallback immediately, then swaps in the web font when it arrives.
  • Subsetting: Latin only by default. Adding more subsets is one line in the URL but doubles the file size; I only do it if content requires it.
  • Face count: two families maximum (display + body), with at most three weights each. Variable fonts when the family supports them.
  • System fallback stack: every font family declaration ends in a real system fallback (system-ui, -apple-system, "Segoe UI", Roboto, sans-serif) so the first paint is always readable.
  • Cache: Bunny serves with a year-long cache. Returning visitors don't re-download.

CSS architecture

One stylesheet, vanilla CSS, custom properties, partials concatenated at build. No framework, no preprocessor, no CSS-in-JS, no PostCSS. Just CSS.

  • Partials live in src/assets/css/ organized by concern: reset.css, tokens.css, utilities.css, components/*.css, pages/*.css. Each file owns its concern and nothing else.
  • The entry point (global.css) is mostly a comment. A custom Eleventy template extension reads each partial in declared order with fs.readFileSync and emits a single concatenated, minified file.
  • Why no @import: the browser blocks rendering on every @import chain, creating a request waterfall. Concatenation at build time avoids this entirely.
  • Custom properties everywhere. Color, type scale, spacing scale, radius, shadow, transition, breakpoint variables are all in tokens.css. Themes (light/dark) flip the values, not the components.
  • Fluid type and spacing via clamp(). Most type tokens are clamp(min, preferred, max) so they scale with viewport without media-query breakpoints.
  • Container queries for components that need to respond to their parent rather than the viewport. Mostly used for cards in flexible grids.
  • Cascade layers (@layer reset, tokens, utilities, components, pages) make specificity predictable. Late-loaded utilities never accidentally outrank components.
  • Cache busting: the stylesheet ships with a content-hash query string (style.css?v=4a701c154dc9) so a deploy invalidates the cache for returning visitors atomically.

JavaScript architecture

JavaScript exists to make a few interactions nicer. The site works fully without it. Specifically:

  • No framework, no router, no state library. Every script is a small, self-contained module.
  • Defer + module type. All scripts are loaded with defer or as type="module" so they never block parsing.
  • Inline no-flash theme script in <head>. Reads localStorage + prefers-color-scheme and sets data-theme on <html> before paint. Prevents the dark-to-light flash.
  • Inline event handlers as a fallback for hamburger toggles, search opens, and theme toggles. The minifier can occasionally break event-listener binding for deferred scripts; inline onclick attributes act as a belt-and-suspenders safety net.
  • IntersectionObserver for blog TOC: the sticky table of contents updates the active section as the visitor scrolls. No scroll-event listeners.
  • Pagefind UI: bundled by Pagefind itself as a small ESM module. Loaded only on pages with a #search container.
  • Reading progress bar, copy-link buttons, social share on blog posts. All vanilla, all under 2 KB combined.
  • No tracking pixels. No third-party advertising scripts. Analytics is server-side or first-party.

Theming & dark mode

Light/dark themes are first-class. Theme tokens live in tokens.css as CSS custom properties; :root[data-theme="dark"] overrides the values. Components reference tokens, never raw colors.

  • Toggle behavior: three states (Light, System, Dark) via a segmented control. System tracks prefers-color-scheme live.
  • Persistence: the user's choice is written to localStorage. The next visit applies it before paint.
  • No flash: a tiny inline script in <head> reads the stored choice and sets data-theme before the body renders. Without this, the page momentarily renders in the wrong theme.
  • Reduced motion: a global rule disables transitions and animations when prefers-reduced-motion: reduce is set. I test this with the OS-level setting enabled.
  • Forced colors: I test in Windows High Contrast mode; system colors are honored where appropriate via forced-colors: active.
  • Color-scheme meta: <meta name="color-scheme" content="light dark"> tells the browser the intent so form controls and scrollbars adapt automatically.

Pagefind generates a static index at build time. Search runs entirely in the visitor's browser with no server, no API, no cookies.

  • Index size: typically 50 to 250 KB total for a small-business site, served as small JSON chunks fetched on demand.
  • Indexed content: every page in collections.all minus excluded paths (style guide, brand guidelines, sitemap, etc.). Front-matter tags become filters when I wire them.
  • Build hook: Pagefind runs in an eleventy.after hook so Cloudflare Pages' default build preset (which runs npx @11ty/eleventy directly) picks it up without me needing a custom command.
  • UI: Pagefind's stock UI module styled to match the host theme. Loaded only on pages with a #search container.
  • Performance: search box is interactive in well under 100 ms because the bundle is small and the index loads lazily.

Form architecture

I do not run a form server. Every form on every site I ship posts directly to Web3Forms, which routes the submission to the client's email.

  • Submission flow: form POSTs multipart/form-data to https://api.web3forms.com/submit with the client's access key. Web3Forms validates, applies rate limits, and emails the client.
  • Spam protection: a hidden honeypot field (name="botcheck") catches the dumb bots. Smart bots are caught by Web3Forms' rate-limit + heuristic. I do not use CAPTCHA; it kills conversion and is mostly defeated anyway.
  • Validation: HTML5 native (required, type="email", pattern). Errors are accessible: invalid fields get aria-invalid="true" and a visible message announced via role="alert".
  • Success behavior: on success the page redirects to /thank-you/?type=<form-name>. The thank-you page reads the type parameter and shows form-specific next-steps.
  • Failure behavior: on network error the form shows an inline error (#form-error) with phone and email fallback so the visitor still has a path to reach you.
  • What I never do: store form submissions on infrastructure I control. I do not see your messages unless you forward one to me. The data lives in your inbox.
  • When Web3Forms isn't enough: for routing logic, conditional fields by zip code, or CRM integration I use a Cloudflare Worker (serverless edge function). Still no traditional server.

SEO architecture

SEO splits cleanly into two halves: technical foundation (binary; you have it or you don't) and ongoing content (gradual). I deliver the foundation; the content is yours.

  • Per-page metadata. Title, description, canonical URL, Open Graph tags, Twitter Card tags. All are set in front-matter or layout defaults; never duplicated across pages.
  • Structured data (JSON-LD): WebSite + LocalBusiness + BreadcrumbList on every page. Service on each service page. BlogPosting on each post. FAQPage on FAQ. Person on author pages. All validated against schema.org and Google's Rich Results Test before launch.
  • Heading hierarchy. One <h1> per page, descending order, no skipped levels. Page sections use <section> with a heading.
  • Sitemap. A custom sitemap.njk emits sitemap.xml by iterating collections.all. Submitted to Google Search Console at launch and refreshed on every deploy.
  • robots.txt. Allows everything by default; references the sitemap. No accidental disallows.
  • Internal linking. Service area pages link to neighboring areas; service pages cross-link related services. Every page is reachable in two clicks from the homepage.
  • URL hygiene. Trailing slash, lowercase, hyphenated, never abbreviated. /services/water-heater-install/, not /svcs/whInstall.
  • Mobile-first. Google's index is mobile-first; my designs are too.

Accessibility pipeline

The working baseline is WCAG 2.2 Level AA. I test against it on every site before launch and every time I ship a significant change. Methods:

  • Automated audits: Lighthouse (Chrome DevTools), axe DevTools, and WAVE. I aim for zero criticals and zero serious issues.
  • Keyboard-only navigation from the first focusable element to the last, on every page. Every interactive control must be reachable and operable.
  • Screen reader testing: VoiceOver on macOS and iOS, NVDA on Windows. I read every page top to bottom to catch landmark, heading, and label issues.
  • Contrast verification using TPGi Colour Contrast Analyser against 4.5:1 (normal text) and 3:1 (large text / UI components). Every text/background combination in both light and dark modes.
  • Zoom testing: up to 200% browser zoom without horizontal scrolling and without content clipping.
  • Reduced-motion: every animation and transition is disabled when prefers-reduced-motion: reduce is set. I test this with the OS-level setting enabled.
  • Forced-colors testing: Windows High Contrast mode pages render legibly; I use forced-colors: active media queries where component styling needs to defer to system colors.
  • Touch targets: 44x44 CSS pixel minimum on every interactive element.

What that means in practice

  • Semantic HTML: proper <nav>, <main>, <article>, <header>, <footer> landmarks; heading order; lists that are lists; buttons that are buttons.
  • Skip-to-content link as the first focusable element.
  • Visible focus rings (3 px gold ring at 3 px offset) on every interactive control.
  • 44x44 CSS pixel minimum tap targets.
  • Form labels bound to inputs via for/id. Errors announced via role="alert".
  • Radio/checkbox groups wrapped in role="radiogroup" / role="group" with aria-labelledby pointing at the visible label.
  • Decorative SVGs get aria-hidden="true" and focusable="false". Meaningful images get real alt.
  • Color is never the only way information is conveyed.
  • aria-current="page" on the active nav link so screen readers announce position.

Security posture

The easiest-to-hack code is the code that doesn't exist. I keep my attack surface close to zero by staying static.

  • No server-side runtime. No PHP, no Node runtime, no application server on the hot path. There's nothing to RCE, no database to SQL-inject, no session store to hijack.
  • No plugins. WordPress plugin ecosystems are a decade-long source of CVEs. I have zero of them.
  • No database. No credentials to leak, no backups to orchestrate, no PII retained on infrastructure I control. Web3Forms submissions flow straight to client email.
  • HTTPS on day one. Automatic SSL via Cloudflare's managed certs. HSTS headers with preload and includeSubDomains; max-age 12 months.
  • Content Security Policy & security headers configured via _headers for every site:
    • Content-Security-Policy tightly scoped to known origins.
    • X-Frame-Options: DENY + frame-ancestors 'none'.
    • X-Content-Type-Options: nosniff.
    • Referrer-Policy: strict-origin-when-cross-origin.
    • Permissions-Policy denies camera, microphone, geolocation, and other powerful APIs by default.
  • security.txt shipped on every site so researchers know how to responsibly disclose.
  • Dependabot on every repo for alerting on npm package vulnerabilities. Surface area is small (a handful of build-time dev dependencies, zero runtime deps) but I track them.
  • Signed commits. Every commit is GPG-signed and verified by GitHub.

Caching & invalidation

Cloudflare's edge caches static assets aggressively. I make sure invalidation is atomic so a deploy never serves a half-stale page.

  • HTML: served with Cache-Control: public, max-age=0, must-revalidate so the edge revalidates on every request. Cloudflare returns a 304 if unchanged; the round-trip is sub-50 ms.
  • CSS & JS: served with Cache-Control: public, max-age=31536000, immutable. URLs include a content-hash query string, so a new deploy automatically invalidates returning visitors' caches without serving stale assets.
  • Images: served with the same immutable policy. Filenames are content-hashed; nothing collides.
  • Fonts: served by Bunny with a year-long cache.
  • Search index: Pagefind chunks are content-hashed and immutably cached.

Build & deploy pipeline

  1. Commit. Changes get reviewed and committed to the main branch on GitHub. I sign every commit.
  2. CI trigger. Cloudflare Pages detects the push and spins up a clean build container.
  3. Build. npx @11ty/eleventy compiles templates, processes images into AVIF/WebP/JPEG responsive variants, generates favicons, writes the RSS feed, writes the sitemap.xml and HTML sitemap.
  4. Minification. In production, HTML/CSS/JS runs through html-minifier-terser with conservative settings (preserves semantic whitespace where it matters).
  5. Pagefind indexing. Runs in an eleventy.after hook so the Cloudflare Pages preset works out of the box. Generates /pagefind/* with pre-indexed search chunks.
  6. Cache busting. eleventy-auto-cache-buster appends a content-hash query string to every stylesheet and script reference so upgrades ship immediately to returning visitors without stale assets.
  7. Deploy. Cloudflare pushes the build to the global edge in under 60 seconds. A preview URL is generated for every non-main branch so I can share in-progress work with the client.
  8. Roll-back. Every prior deploy remains available; if anything is wrong I revert in one click.

Environments & branching

  • main: production. Auto-deploys to the live URL on every push.
  • Feature branches: any branch name. Auto-deploys to <branch>.<project>.pages.dev. Used for client review of in-progress changes.
  • Pull requests: get their own preview URL plus a commented-back link. I share these with clients before merging.
  • Local dev: npm run dev or npm start. Cross-platform commands (no ELEVENTY_ENV= prefix; that's bash-only). Hot reload via 11ty's built-in server.
  • Environment variables: SITE_URL defaults to a sensible value but can be overridden per environment. Secrets (Web3Forms keys, analytics IDs) live in environment variables, never in the repo.

Measurement & monitoring

  • Core Web Vitals via Cloudflare's server-side RUM. Real-user data, aggregated across the entire visitor population, no client-side cookies needed.
  • Uptime monitoring via Cloudflare's status API plus an external ping monitor (UptimeRobot) that alerts me by SMS if the site is down.
  • Google Search Console connected at launch for every client, so I can see actual search impressions, clicks, and Core Web Vitals reports from Google's own crawl data.
  • Bing Webmaster Tools: underrated, still worth ~10 to 15% of service-business traffic depending on the industry.
  • Lighthouse CI on major changes to catch regressions in performance or accessibility before they go live.
  • Form-submission monitoring: Web3Forms reports submission counts; if a form goes silent for 10 days I email the client to verify it's still working.
  • Domain expiration monitoring: I watch WHOIS for every client domain I manage, so renewal reminders don't depend on the registrar's email.

Browser support matrix

  • Tier 1 (full support): the latest two versions of Chrome, Edge, Safari, Firefox on desktop and mobile.
  • Tier 2 (graceful degradation): Chromium-based browsers from 2 years back. CSS custom properties, Grid, Flexbox, container queries (with fallback), and the AVIF/WebP image stack are all supported.
  • Tier 3 (functional but ugly): very old browsers (IE-era) get unstyled, semantic HTML. The site is still readable; it just doesn't have the typography and spacing.
  • No-JS: every page works fully without JavaScript. Search and the theme toggle degrade gracefully; everything else (nav, forms, content) works the same.
  • Reduced-data & reduced-motion: respected via media queries.

Failure modes

Every system has failure modes. Honest disclosure of ours:

  • Web3Forms outage would prevent form submissions until they recover. The page shows a fallback message with phone and email so visitors aren't stranded. Web3Forms' uptime in my experience: 99.95%+, with no incident lasting more than 30 minutes.
  • Cloudflare regional outage would affect visitors in that region. I monitor Cloudflare's status feed and post a status note on Twitter/X if I see one. The site is unaffected for visitors in other regions.
  • Bunny Fonts outage means the site renders in the system font fallback (which is the design's "B-tier" appearance, still readable, still on-brand). No layout shift.
  • DNS misconfiguration during a registrar transfer is the single biggest risk window for any site move. I coordinate transfers carefully and run with a 5-minute TTL during the change so I can roll back fast.
  • A client domain expiring would take the site offline within 24 to 48 hours. I monitor WHOIS expiration directly and remind clients at 60, 30, and 7 days.
  • A typo committed to main would deploy. I mitigate via PR previews, my own pre-deploy checks, and one-click rollback. The longest exposure window I have ever had to a typo on production: 90 seconds, and the typo was in a footer copyright line.

Technical Q&A

Why 11ty over Astro?

Astro is a great tool; if it had existed when I started I might have picked it. Today, for service-business marketing sites, 11ty has less abstraction overhead, a stabler plugin ecosystem for my use case, and equivalent output. I would evaluate Astro case-by-case for anything that genuinely benefits from islands; most of my builds don't.

Why no JavaScript framework?

My sites don't have application state. There's no shopping cart, no multi-step form with validation across pages, no dashboard. Everything a framework is good at, I do not need. Progressive enhancement with a few hundred lines of vanilla JS covers every interaction on my sites: theme toggle, mobile menu, FAQ accordion, form submission, site search, glossary tooltips. A framework would add 30 to 100 KB of runtime and force me to care about hydration; that's a large cost for no benefit.

How do you handle a blog with 200+ posts?

11ty handles it fine; I have done it. Build times scale roughly linearly with post count. A 200-post blog builds in under 10 seconds on Cloudflare's CI. Incremental builds (the --incremental flag) are under a second locally while authoring. Pagefind indexes the entire corpus in milliseconds because it operates on the static HTML, not the source templates.

What if I need a contact form that does something other than email?

Web3Forms covers roughly 95% of small-business form needs. For the other 5% (CRM integrations, automated routing by zip code, conditional logic between steps), I use Cloudflare Workers (serverless edge functions). Still no server to maintain. For anything genuinely app-shaped (booking calendars, live chat, client portals with accounts), I integrate purpose-built SaaS (Calendly, Tawk.to, etc.) rather than building from scratch.

Can I see the source code before I buy?

Yes. The site you're reading is representative of what I build. You can view source in your browser. I am happy to share the GitHub repo of this site on request.

What's in your build pipeline that a typical agency doesn't have?

Honestly, nothing exotic. My edge is discipline, not novelty. I commit more time to measurement (Lighthouse CI, real-user monitoring, manual accessibility testing) than to shiny tooling. The right tools for a marketing site have been stable for five years; I have just chosen to use them well.

Can I run my own hosting?

Yes. The output is plain static files. You can host the build on any CDN (Netlify, Vercel, S3+CloudFront, GitHub Pages, your own nginx). I deploy to Cloudflare Pages by default because the cost is zero and the edge is excellent; I do not lock you to it.

Can I migrate off your stack later?

Easier than most. The site is plain HTML/CSS/JS once built. Content lives in Markdown files. There's no proprietary database export step. A junior developer could pick up the source repo and continue maintaining it. I would help with the handoff at no charge; I would rather keep a friendly relationship than make you stay through friction.

Do you do A/B testing?

Yes, lightweight. I deploy two versions of a page on different URLs and split traffic via a Cloudflare Worker. I do not bolt on Optimizely or VWO unless the testing volume justifies the runtime weight (it almost never does for a small business).

How do you handle user-generated content?

I do not, by default. If a client needs comments, reviews, or user submissions on the public site, I wire in a purpose-built service (Disqus, GoRated, native Google Reviews via API) rather than running my own moderation. UGC is a different operational burden than static publishing; I treat it as a separate feature, not a baseline.

What's the worst thing about your approach?

Dynamic content from non-technical clients is a weak point. If a client's use case genuinely needs "edit in a browser" content management (say, a 30-person team that each needs to write blog posts), the "email me what to change" model doesn't scale. In that narrow case I either bolt on a headless CMS (Sanity, Decap) or recommend the client work with an agency that specializes in WordPress or a full-fledged CMS. Most small-business clients don't have this problem; most small-business "CMS needs" are better served by three emails a month to the person who built the site.

Can I see your build config?

Yes. eleventy.config.js in any of my repos is the entry point. I share it on request. There are no secrets in it; secrets live in environment variables.

The code is the pitch.

If you want to view source, pull the GitHub repo, run Lighthouse yourself, or ask specific technical questions, I am happy to engage at that level. No sales filter.