Skip to content

Latest commit

 

History

History
239 lines (187 loc) · 8.34 KB

File metadata and controls

239 lines (187 loc) · 8.34 KB

Logo

GitHub Stars GitHub forks GitHub Last Commit GitHub licence

The source for vanityURLs.link — bilingual (EN/FR) documentation site built with Hugo and deployed on Cloudflare Pages.

Requirements

  • Hugo 0.158.0+ (extended edition). Production is pinned to 0.160.1 in build.sh; hugo.yml declares min_version: 0.158.0.
  • Node.js 20+ (production uses 24.14.1). Needed for Tailwind/PostCSS and pagefind.
  • Go 1.23+ (production uses 1.26.1). Needed for Hugo modules.
brew install hugo node       # macOS
npm install                  # install devDependencies

Local development

npm run dev          # hugo server with drafts
npm run dev:search   # same + build pagefind index first (needed for ⌘K)
npm run build        # production: hugo --gc --minify && pagefind
npm run clean        # remove public/ and resources/_gen/

The pagefind index (public/pagefind/) is generated by the build. Don't commit static/pagefind/ — it's in .gitignore.

Linting

npm run lint          # markdown + yaml + spell
npm run lint:md:fix   # auto-fix markdown
npm run lint:links    # lychee link checker
npm run lint:secrets  # gitleaks secret scanner

Key features

Documentation

  • Multi-level sidebar driven by data/{en,fr}/docs_nav.yaml — paths are language-neutral
  • Table of contents, breadcrumbs, Edit-on-GitHub, prev/next, mobile <select> dropdown

Blog

  • Featured post via featured: true front matter (one per language)
  • Reading progress bar, social share (X, LinkedIn, copy-link), related posts, tags, RSS

Showcase

  • Client-side tag filter with count badges

i18n

  • Bilingual content: page.en.md / page.fr.md side-by-side
  • UI strings in i18n/en.yaml and i18n/fr.yaml (45+ keys with pluralization)
  • Localized dates via date_format_long i18n key
  • Language-neutral data file paths (layouts prepend /en/ or /fr/ via relLangURL)
  • Language switcher preserves current page when translation exists

UX / Accessibility

  • Dark mode with no-flash-on-load
  • Copy-to-clipboard on every <pre>
  • ⌘K search via Pagefind
  • Skip-to-content link, arrow-key sidebar nav, anchor hover

SEO / Performance

  • hreflang, Open Graph, JSON-LD (SoftwareApplication, TechArticle, BreadcrumbList)
  • Favicon + apple-touch-icon from /logo.svg
  • Language-scoped PWA manifest
  • Fingerprinted + minified CSS with SRI

Shortcodes

{{< callout type="warning" title="Breaking change" >}}
This option was removed in v2.
{{< /callout >}}

{{< code file="config/deploy.yml" lang="yaml" >}}
service: my-app
{{< /code >}}

{{< details title="Why not Kubernetes?" >}}
Kubernetes is overkill for most teams.
{{< /details >}}

{{< cards cols="3" >}}
{{< card title="Installation" icon="download" href="/docs/installation/" >}}
Get up and running in minutes.
{{< /card >}}
{{< /cards >}}

{{< filetree/container >}}
  {{< filetree/folder name="config" >}}
    {{< filetree/file name="deploy.yml" annotation="// edit this" >}}
  {{< /filetree/folder >}}
{{< /filetree/container >}}

Available: callout, code, card + cards, details, filetree/*.

Adding content

New blog post

hugo new content blog/my-post.en.md
hugo new content blog/my-post.fr.md

Front matter:

title: "My Post Title"
date: 2026-01-15
author: "Benoît H. Dicaire"
description: "One-sentence description for cards and meta."
tags: ["guide"]
featured: false

Set exactly one post as featured: true per language.

New showcase entry

title: "My Company"
description: "What they do and why they use vanityURLs."
site_url: "https://example.com"
color: "#0369a1"
logo_char: "M"
tags: ["personal"]
featured: false

New docs page

  1. Create content/docs/my-section/my-page.{en,fr}.md with title, description, nav_order.
  2. Register in both data/en/docs_nav.yaml and data/fr/docs_nav.yaml. Paths are language-neutral:
- title: My New Page
  url: /docs/my-section/my-page/
  children:
    - title: Sub-topic
      url: /docs/my-section/my-page/sub-topic/

Deployment

Cloudflare Workers Static Assets via wrangler.toml. Build command is ./build.sh, which installs pinned Dart Sass / Go / Hugo / Node.js and then runs hugo build --gc --minify followed by npx pagefind --site public. Assets directory is ./public; custom domain is vanityurls.link.

A small edge Worker at src/worker.mjs wraps HTML page requests and emits a server-side Umami pageview event via ctx.waitUntil() — no client-side JS, no cookies, invisible to ad blockers. Asset requests (CSS, JS, fonts, Pagefind chunks, images, XML feeds) bypass the Worker via negative patterns in assets.run_worker_first, so they remain free static-asset reads.

The Worker needs two secrets set once per environment:

wrangler secret put UMAMI_WEBSITE_ID
# → paste the UUID from the Umami dashboard (Settings → Websites)

wrangler secret put UMAMI_ENDPOINT
# → https://cloud.umami.is/api/send   (for Umami Cloud)

If either secret is missing, the Worker still serves pages correctly — it just skips the tracking call. This means local wrangler dev runs don't pollute production analytics.

See the Hugo on Cloudflare guide for context.

Worker smoke tests

npm test

Runs src/worker.test.mjs, which exercises the routing, payload shape, and edge cases (non-HTML response, non-GET methods, missing secrets) without a Cloudflare dependency.

Project layout

.
├── hugo.yml                     # Site config
├── build.sh                     # Cloudflare build script
├── tailwind.config.js
├── postcss.config.js
├── wrangler.toml                # Worker + static assets config
│
├── src/
│   ├── worker.mjs                # Edge Worker: server-side Umami tracking
│   └── worker.test.mjs           # Node-runnable smoke tests (`npm test`)
│
├── assets/css/main.css
│
├── i18n/
│   ├── en.yml                   # English UI strings
│   └── fr.yml                   # French UI strings
│
├── data/
│   ├── docs_nav.{en,fr}.yml     # Sidebar (language-neutral paths, translated titles)
│   ├── home.{en,fr}.yml
│   ├── trust.{en,fr}.yml
│   └── docs_index.{en,fr}.yml
│
├── layouts/
│   ├── index.html
│   ├── 404.html
│   ├── _default/                # baseof, single, taxonomy, trust
│   ├── blog/                    # list + single
│   ├── docs/                    # list + single
│   ├── showcase/                # list + single
│   ├── tags/                    # list + taxonomy
│   ├── shortcodes/              # callout, code, card, cards, details, filetree/*
│   └── partials/                # head, header, footer, search*, jsonld, lang-switcher
│
├── content/                     # .en.md and .fr.md pairs throughout
│   ├── blog/
│   ├── docs/
│   ├── showcase/
│   └── trust/accessibility/impressum/license/privacy/security/terms/vulnerability/contributing
│
├── static/
│   ├── _headers                 # CSP + cache rules
│   ├── _redirects               # Short-path 301s
│   ├── logo.svg
│   ├── social.png
│   ├── site.webmanifest
│   └── .well-known/security.txt
│
└── .github/workflows/
    └── release-please.yml       # Conventional commits → changelog + version

Contributing

For major changes, please open an issue first. See contribution guidelines and code of conduct.

License

Copyright © 2026 Benoît H. Dicaire. Licensed under the MIT license. See LICENSE.md.