DTRH.net

Avatar

Client Showoff

CLIENT SHOWOFF

Project documentation for a site built and maintained by DTRH.net. The overview below describes the client's stack and delivery — not this portfolio site.

Client Site

Visit the client's live build at https://elginauctions.com

Open Client Site

Client project overview (markdown)

Elgin Auctions — Project Overview

A modern, maintainable static website with a code-free editorial interface, Git-backed version history, and a fully mobile-optimized CMS admin panel.


Table of Contents

  1. What This Project Is
  2. Why We Rebuilt from Wix
  3. Technology Stack
  4. Architecture Overview
  5. Content Management System
  6. Authentication — Clerk Integration
  7. GitHub-Backed Publishing Pipeline
  8. Mobile-Responsive Admin Panel
  9. Performance & SEO
  10. Developer Workflow
  11. Deployment — Vercel
  12. Security Model

1. What This Project Is

Elgin Auctions is a static website built for a professional auction house. It serves as the business's primary online presence, providing:

  • Public-facing pages: Home, Next Auction, Consignment, Contact / Directions, and News.
  • A password-protected content management interface at /admin where staff can update auction listings, site copy, contact details, and news entries — all without writing code.
  • An automated publish pipeline: every edit saved in the admin panel creates a commit in GitHub, which triggers Vercel to rebuild and redeploy the live site — typically within one to three minutes.

2. Why We Rebuilt from Wix

The original site was hosted on Wix, a closed, proprietary drag-and-drop platform. This created several pain points:

Problem with Wix How This Build Fixes It
Proprietary lock-in — content and design trapped in Wix's format All content lives in plain Markdown and JSON files in GitHub. Portable to any host.
Poor Core Web Vitals — Wix pages ship large JS bundles that hurt load speed Eleventy outputs pure static HTML with zero JavaScript framework overhead
No version history or rollback Every publish is a Git commit — revert any change in seconds
Limited SEO control Full control over <head>, JSON-LD structured data, Open Graph tags, meta descriptions, canonical URLs
Per-seat or plan pricing for advanced features Open-source tooling (Eleventy, Decap CMS, GitHub) with Vercel's free/hobby tier
No programmatic content model CMS collections, schemas, and field types defined in a single YAML file

3. Technology Stack

Layer Technology Role
Static site generator Eleventy (11ty) v3 Compiles Nunjucks templates + Markdown + JSON data into plain HTML/CSS/JS
Templating Nunjucks (.njk) Layout inheritance, partials, loops, conditionals
Content format Markdown + JSON front matter Human-readable, Git-diff friendly
CMS Decap CMS v3 Browser-based UI at /admin — no server required at edit time
Authentication Clerk Identity provider — replaces GitHub OAuth with secure email-based sign-in
GitHub backend GitHub REST API Every CMS save = a signed commit on the repository
Hosting Vercel Instant global CDN deploys triggered by Git commits
API proxy Custom Express handler (/api/github-proxy) Holds the GitHub PAT server-side — the browser never sees it
DNS Cloudflare (or configurable) Fast DNS, optional DDoS protection

4. Architecture Overview

Editor (browser)
    │
    ▼
/admin — Decap CMS (React SPA, served as static HTML)
    │  signs in via Clerk (email + password)
    │
    ▼
/api/github-proxy  (Vercel serverless function)
    │  verifies Clerk JWT session
    │  rewrites GitHub API calls using DECAP_GITHUB_TOKEN (server-side PAT)
    │
    ▼
GitHub REST API → DTRHnet/Elgin-Auctions_wix
    │  commit on main branch (or DECAP_GITHUB_BRANCH)
    │
    ▼
Vercel  →  npm run build  →  npx @11ty/eleventy
    │  reads src/_data/site.json, src/content/auctions/*.md, etc.
    │
    ▼
_site/  (pure static HTML, CSS, JS)  →  global CDN edge

Key design decision: The browser never holds a GitHub token. All GitHub API calls are proxied through a Vercel serverless function that verifies the Clerk session and makes the actual GitHub request using a server-side Personal Access Token (DECAP_GITHUB_TOKEN). This prevents credential leakage even if the browser's network traffic is intercepted.


5. Content Management System

The CMS is powered by Decap CMS v3 — an open-source, Git-backed headless CMS that runs entirely in the browser as a React single-page application. Editors navigate to /admin, sign in, and see a structured form interface for every piece of content on the site.

Collections

Site & Home (global)

Stored in src/_data/site.json. One file controls:

  • Business contact info — phone numbers (display, tel: link, E.164 schema), email, address lines.
  • Social & media — Open Graph image URL, logo URL, Instagram link, Google Maps embed URL.
  • Home page sections (all nested under home.*):
    • SEO metadata: browser title, meta description, OG title/description, JSON-LD webpage name.
    • Hero section — badge label, main H1 heading, subline.
    • About section — two paragraphs, hours card label/time, testimonial quote and citation.
    • Video sections — repeatable list: title, lead text, embed URL, thumbnail.
    • Auctions section header — label and title above the auction card grid.
    • House calls strip — title, lead paragraph, service area description.
    • Recycles section — title, subtitle, three paragraphs, and a repeatable bullet list of accepted items.
    • Footer / contact strip — HTML-enabled hours block, footer tagline, short hours string.
  • Navigation menu — ordered list of menu items with label, page relation or custom URL, new-tab toggle, visibility flag, and sort order.

Auction Cards (home)

Stored as individual Markdown files in src/content/auctions/. Each file represents one card on the home page auction grid. Fields:

  • Card title (H3)
  • Badge text and style (Live / Upcoming / Booked — drives CSS colour class)
  • Optional external link (e.g. MaxSold bidding page) and link label
  • Numeric sort order (lower = first)
  • Description body (full Markdown)

Adding a new auction event takes under 60 seconds: click New Auction card, fill in the form, click Update.

News Entries

Stored in src/content/news/. Fields: title, excerpt, publish date, status (draft/live), featured image + alt text, and a full Markdown body. Status and publishDate are evaluated at build time — drafts and future-dated posts are automatically suppressed from the public site.

Interior Pages

The Next Auction, Consignment, and Contact / Directions pages are stored as Markdown files directly in src/. Each has: layout (hidden), permalink (hidden), page title, hero H1 title, hero subline, and a rich Markdown body below the hero. The CMS enforces the layout/permalink fields as hidden so editors cannot accidentally break the routing.


6. Authentication — Clerk Integration

Instead of using Decap CMS's built-in GitHub OAuth flow (which would expose a GitHub token in the redirect URL and require each editor to have a GitHub account), this project uses Clerk as the identity provider.

How it works:

  1. When an editor loads /admin, the page checks for a valid Clerk session.
  2. If not signed in, Clerk mounts a hosted Sign-In component — editors use an email and password (or magic link / SSO configured in Clerk's dashboard).
  3. After sign-in, Clerk issues a short-lived JWT stored in the browser session.
  4. Every CMS API call includes this JWT as a Bearer token in the Authorization header.
  5. The /api/github-proxy serverless function calls clerk.authenticateRequest() to verify the JWT before touching GitHub.
  6. An optional DECAP_ALLOWED_EMAILS environment variable restricts which Clerk accounts can access the CMS — useful for limiting access to specific staff addresses.

Why this is better than GitHub OAuth:

  • Editors do not need GitHub accounts.
  • No GitHub token is ever sent to the browser.
  • Access can be revoked instantly by removing the user from Clerk — no need to rotate GitHub tokens.
  • Clerk handles MFA, session expiry, and email verification.

7. GitHub-Backed Publishing Pipeline

Every "Publish" click in the CMS triggers this chain:

Editor clicks Update / Publish
    │
    ▼
Decap CMS sends PATCH/PUT to /api/github-proxy/repos/{owner}/{repo}/contents/{path}
    │  Authorization: Bearer <Clerk JWT>
    │
    ▼
/api/github-proxy verifies JWT with Clerk, calls GitHub API with DECAP_GITHUB_TOKEN
    │
    ▼
GitHub creates a commit on the configured branch (main / DECAP_GITHUB_BRANCH)
    │
    ▼
Vercel detects push webhook → triggers npm run build
    │  npx @11ty/eleventy
    │  reads updated src/content/, src/_data/site.json, etc.
    │
    ▼
_site/ rebuilt → deployed to Vercel's global CDN edge
    │
    ▼
Live site updated — typically within 60–180 seconds

Rollback: Because every publish is a Git commit, rolling back to any previous state is a single git revert or GitHub "Revert" button away. No database dumps, no manual backups.


8. Mobile-Responsive Admin Panel

One of the most significant recent improvements is making the Decap CMS admin panel fully usable on mobile devices and tablets — something the CMS does not provide natively.

The Problem

Decap CMS is a React single-page application designed primarily for desktop use. On viewports under ~1024px wide:

  • The collections sidebar occupied fixed horizontal space, overlapping the editing area.
  • Content entry lists rendered as wide HTML tables that caused horizontal scrolling.
  • Form inputs were too small for touch interaction.
  • Modal dialogs and dropdown menus overflowed the screen.
  • Buttons had touch targets below the 44px WCAG-recommended minimum.

The Solution

Rather than forking the CMS source (which would require maintaining a custom build forever), we injected custom CSS overrides and vanilla JavaScript enhancers directly into src/admin/index.html. Decap CMS uses stable substring-matched class names (e.g. [class*="SidebarContainer"]) which are reliable override targets.

Mobile Detection (dual-method)

A synchronous inline script runs in <head> before first paint so classes are applied before the page renders — eliminating flash of unstyled content:

// Resolution check: viewport width ≤ 1024px
var isMobileRes = window.innerWidth <= 1024;

// User-agent check: standard mobile/tablet UA strings
var isMobileUa = /Android|iPhone|iPad|iPod|Mobile|Tablet/i.test(navigator.userAgent);

// Touch check: device supports touch events
var isTouch = ('ontouchstart' in window) || (navigator.maxTouchPoints > 0);

Classes added to <html>:

  • ea-mobile-res — viewport width ≤ 1024px
  • ea-mobile-ua — mobile user-agent detected
  • ea-is-mobile — combined: res or (ua and touch)

A resize event listener re-evaluates on orientation change and browser resize (important for DevTools responsive emulator).

Slide-In Sidebar Drawer

On mobile, the collections sidebar transforms from a static panel into a full-height slide-in drawer:

  • Fixed positioned, slides in from the left with a smooth cubic-bezier transition.
  • A hamburger button (☰) is dynamically injected into the app header by a MutationObserver that waits for React to render the header.
  • Clicking the hamburger toggles the ea-sidebar-open class on <html>.
  • A translucent backdrop overlay appears behind the drawer with backdrop-filter: blur(2px) — clicking it closes the sidebar.
  • Navigating to a collection (clicking any sidebar link) automatically closes the drawer after 120ms.
  • The <html> element gets overflow: hidden while the drawer is open to prevent scroll bleed.

Entry Lists → Expandable Cards

Decap renders content entry lists as HTML tables. On mobile these are transformed into stacked card layouts entirely via CSS:

  • <thead> / table headers are hidden.
  • <table> and <tbody> become block containers.
  • Each <tr> becomes a rounded card with box shadow, hover lift animation, and border-radius: 12px.
  • The first <td> (entry title) is styled as a bold card header.
  • Subsequent cells (date, status, etc.) are hidden by default — collapsed below the title.

A MutationObserver-powered JavaScript enhancer:

  1. Reads the (hidden) column labels from the table <thead>.
  2. Sets data-label attributes on each body cell.
  3. Injects a chevron button (▾) into each card row.
  4. On chevron click: toggles ea-expanded on the row (expanding the hidden cells) with e.stopPropagation() so the click doesn't navigate to the editor — the user must tap the card title or an explicit edit link to enter the form.
  5. ::before pseudo-elements on expanded cells use content: attr(data-label) to prefix each value with its column label (e.g. STATUS · Live).

Other Mobile Fixes

Issue Fix
Horizontal overflow overflow-x: hidden on html, body, #nc-root
Split-pane preview Preview pane hidden on mobile (CSS display: none)
Touch targets min-height: 44px on all button, a elements
Form inputs font-size: 16px prevents iOS auto-zoom on input focus
Collection header Stacks vertically (flex-direction: column) with full-width "New entry" button
Search bar Full width on mobile
Modals / dialogs width: 92vw, max-height: 80vh, centered with position: fixed; inset: 0
Dropdown menus Fixed to bottom of screen (bottom: 20px) — thumb-friendly
Toolbar buttons flex: 1 1 auto — stretch to fill available width

9. Performance & SEO

Core Web Vitals

Because Eleventy outputs pre-rendered static HTML, there is no client-side hydration delay. Pages load with:

  • Zero JavaScript framework runtime shipped to the browser (React is only used inside the /admin CMS, never on public pages).
  • Minimal, hand-crafted CSS (src/css/style.css) — no utility-class framework overhead.
  • Images served from /images/uploads/ — developers can add <img loading="lazy"> and responsive srcset as needed.

SEO

Every page includes:

  • Unique <title> and <meta name="description"> pulled from CMS-editable fields.
  • Open Graph (og:title, og:description, og:image) for social sharing previews.
  • application/ld+json structured data (LocalBusiness, WebPage, etc.) — editable per page via the CMS.
  • A canonical <link rel="canonical"> built from site.productionUrl.
  • robots.txt and sitemap.xml committed to the repository and served at the root.
  • Semantic HTML5 structure: single <h1> per page, <nav>, <main>, <footer>, <article>.

10. Developer Workflow

Local Development

# Install dependencies
npm install

# Start the Eleventy static site with file watching (no CMS backend)
npm run dev

# Start with Clerk + GitHub proxy + CMS (requires .env.local with real keys)
npm run dev:cms

npm run dev:cms starts an Express server on http://localhost:3000 (or $LOCAL_CMS_PORT) that:

  • Watches Eleventy for file changes and rebuilds.
  • Patches /admin/config.yml at request time with backend.base_url = http://localhost:3000.
  • Serves /api/github-proxy and /api/clerk-me locally.
  • Injects Clerk browser scripts from local node_modules.

Environment Variables

Variable Required Purpose
CLERK_PUBLISHABLE_KEY Yes Clerk frontend key (public, safe in browser)
CLERK_SECRET_KEY Yes Clerk backend key (server only, never to browser)
DECAP_GITHUB_TOKEN Yes GitHub PAT with repo write access (server only)
DECAP_BASE_URL Recommended Production origin for backend.base_url
DECAP_GITHUB_BRANCH Optional Pin CMS writes to a specific branch
DECAP_ALLOWED_EMAILS Optional Comma-separated whitelist of CMS editor emails

11. Deployment — Vercel

The project is deployed on Vercel with:

  • Build command: npm run build (runs Eleventy)
  • Output directory: _site
  • Framework: None (Vercel treats it as a plain static site)
  • Serverless functions: /api/github-proxy and /api/clerk-me are auto-detected from the api/ directory.

Vercel Rewrites & Redirects (vercel.json)

Route Behaviour
GET /admin 301 redirect → /admin/
GET /config.yml Rewrite → /admin/config.yml (Decap 3.x compat)
/api/github-proxy/:path+ Rewrite → serverless function with __p query param

12. Security Model

Concern Mitigation
GitHub credentials in browser Never happens. DECAP_GITHUB_TOKEN lives only in Vercel env vars and is never sent to the client.
Unauthorized CMS access Clerk session required. DECAP_ALLOWED_EMAILS for additional restriction.
JWT forgery /api/github-proxy calls clerk.authenticateRequest() on every request — forged tokens are rejected.
XSS in CMS output Eleventy auto-escapes template expressions. Raw HTML is only used in explicitly designated fields (contactStripHoursHtml, ldJson).
Search engine indexing of admin <meta name="robots" content="noindex, nofollow"> in src/admin/index.html.
Stale sessions Clerk JWTs are short-lived; admin-bootstrap.js refreshes the token before each GitHub API call and retries on 401.

File Structure Reference

/
├── src/
│   ├── _data/
│   │   ├── site.json          ← all global CMS content (contact, home copy, etc.)
│   │   └── navigation.json    ← CMS-managed menu
│   ├── _includes/
│   │   ├── layouts/
│   │   │   ├── base.njk       ← HTML shell, head, nav, footer
│   │   │   ├── home.njk       ← home page sections
│   │   │   ├── page.njk       ← interior page layout
│   │   │   └── news-post.njk  ← news article layout
│   │   └── partials/          ← reusable template fragments
│   ├── content/
│   │   ├── auctions/          ← one .md per auction card
│   │   └── news/              ← one .md per news entry
│   ├── css/style.css          ← site stylesheet (developer-managed)
│   ├── js/main.js             ← navigation JS (developer-managed)
│   ├── images/uploads/        ← CMS media uploads
│   ├── admin/                 ← CMS source (not Eleventy-processed)
│   │   ├── index.html         ← CMS bootstrap + mobile responsive overrides
│   │   ├── config.yml         ← CMS collection schema
│   │   ├── admin-bootstrap.js ← Clerk + Decap init
│   │   └── preview-templates.js ← CMS live preview components
│   ├── index.njk              ← home page template entry point
│   ├── next-auction.md        ← interior page
│   ├── consignment.md         ← interior page
│   └── contact.md             ← directions page
├── api/
│   ├── github-proxy.js        ← Vercel serverless: Clerk-verified GitHub proxy
│   └── clerk-me.js            ← Vercel serverless: session user info
├── lib/                       ← shared helpers (proxy core, origin resolver, etc.)
├── scripts/                   ← local dev scripts
├── .eleventy.js               ← Eleventy config, collections, afterBuild hook
├── vercel.json                ← build config, rewrites, redirects
└── package.json

Built by DTRH.net. Questions? See the CMS Setup Guide or the Owner Editing Guide.