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
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
- What This Project Is
- Why We Rebuilt from Wix
- Technology Stack
- Architecture Overview
- Content Management System
- Authentication — Clerk Integration
- GitHub-Backed Publishing Pipeline
- Mobile-Responsive Admin Panel
- Performance & SEO
- Developer Workflow
- Deployment — Vercel
- 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
/adminwhere 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:
- When an editor loads
/admin, the page checks for a valid Clerk session. - 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).
- After sign-in, Clerk issues a short-lived JWT stored in the browser session.
- Every CMS API call includes this JWT as a
Bearertoken in theAuthorizationheader. - The
/api/github-proxyserverless function callsclerk.authenticateRequest()to verify the JWT before touching GitHub. - An optional
DECAP_ALLOWED_EMAILSenvironment 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 ≤ 1024pxea-mobile-ua— mobile user-agent detectedea-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
MutationObserverthat waits for React to render the header. - Clicking the hamburger toggles the
ea-sidebar-openclass 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 getsoverflow: hiddenwhile 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, andborder-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:
- Reads the (hidden) column labels from the table
<thead>. - Sets
data-labelattributes on each body cell. - Injects a chevron button (▾) into each card row.
- On chevron click: toggles
ea-expandedon the row (expanding the hidden cells) withe.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. ::beforepseudo-elements on expanded cells usecontent: 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
/adminCMS, 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 responsivesrcsetas 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+jsonstructured data (LocalBusiness, WebPage, etc.) — editable per page via the CMS.- A canonical
<link rel="canonical">built fromsite.productionUrl. robots.txtandsitemap.xmlcommitted 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.ymlat request time withbackend.base_url = http://localhost:3000. - Serves
/api/github-proxyand/api/clerk-melocally. - 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-proxyand/api/clerk-meare auto-detected from theapi/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.