hugo-theme-devrel
DevRel overlay for the Dream Hugo theme.
This is not a standalone theme. It imports Dream as a Hugo module dependency and overlays layouts for posts, talks, talk templates, map, videos, and about. End users only import devrel — Dream is pulled automatically.
Live demos:
- Production site: david.pilato.fr
- Theme
exampleSiteon GitHub Pages: devrel.hugo.pilato.fr
下载主题hugo-theme-devrel-main.zip (访问密码: 3705)

[[module.imports]]
path = "github.com/dadoonet/hugo-theme-devrel"Do not set theme = ["devrel", "dream"]. Do not vendor or fork Dream into this repo.
Dream is MIT-licensed (Copyright © 2019 Yue Yang). Attribution is preserved in LICENSE-DREAM and the site footer.
Features
- Blog posts (
content/posts/…) - Talks with conference metadata, PDF slides, YouTube, social embeds
- Talk templates (multilingual abstracts + “Played N times”)
- Talks map (derived from
conference.latitude/longitude) - Full-text search via Pagefind Component UI (nav loupe → modal, Cmd/Ctrl+K)
- Videos listing
- About page assembled from numbered Markdown sections +
data/socials.toml
Screenshots
Captured from the production site david.pilato.fr
(no browser chrome). Gallery files follow the Hugo themes
spec: images/screenshot.png is 1500×1000 (3:2) and images/tn.png is 900×600 (3:2). README images use absolute raw.githubusercontent.com URLs so they also render on themes.gohugo.io
.
Talks hub — /talks
Featured cards for the latest sessions, then a compact archive, video strip, template previews, and a map summary. This is images/screenshot.png.

All talks — /talks/all
The full archive: decade/year jump links with per-year counts, then a card grid (cover, language, slides/video badges, conference, date). Each year also gets its own Leaflet map so you can see where that year happened, not only the global map.


Videos — /talks/videos
Only talks with a youtube: id. Year navigation (red pills), 16:9 cards with YouTube thumbnails, language flag, event name, and a jump to #video on the talk page.

Talk templates — /talks/templates and /talks/templates/<slug>
The catalog lists every recurring topic, sorted by last played date, with “Played N times”. Open a template for stats (first/last, video count), EN/FR tabs, Talk vs Raw (CFP paste), and the chronological list of conferences.


Search, a talk page, the global map
| Search (Ctrl/Cmd+K) | Talk page |
|---|---|
![]() | ![]() |

Also in images/: homepage (home.png), About (about.png), and the dedicated /search page (search-page.png).
Quick start
Initialize your site as a Hugo module (if needed):
SHhugo mod init github.com/you/your-sitehugo mod init github.com/you/your-siteImport the theme in
hugo.toml:TOML[module] [[module.imports]] path = "github.com/dadoonet/hugo-theme-devrel"[module] [[module.imports]] path = "github.com/dadoonet/hugo-theme-devrel"Set your identity and optional integrations:
TOML[params] author = "Your Name" avatar = "/about/you.avif" headerTitle = "Your Name" motto = "Developer Advocate" email = "you@example.org" siteStartYear = 2024 [params.talks] # Optional remote PDF base (GCS, S3, CDN). Empty = local / page-bundle PDFs. pdf_base_url = "" # Hugo defaults are only tags + categories. Copy this block so series # (and cities / languages) generate pages. Series appears in the nav # overflow when at least one post sets `series:`; drop "series" from # collapseNavItems / reorderNavItems to hide the button. [taxonomies] category = "categories" tag = "tags" series = "series" city = "cities" language = "languages" # Pagefind search (enabled by default). Build index after hugo: # npx pagefind --site public # [params.search] # enabled = true[params] author = "Your Name" avatar = "/about/you.avif" headerTitle = "Your Name" motto = "Developer Advocate" email = "you@example.org" siteStartYear = 2024 [params.talks] # Optional remote PDF base (GCS, S3, CDN). Empty = local / page-bundle PDFs. pdf_base_url = "" # Hugo defaults are only tags + categories. Copy this block so series # (and cities / languages) generate pages. Series appears in the nav # overflow when at least one post sets `series:`; drop "series" from # collapseNavItems / reorderNavItems to hide the button. [taxonomies] category = "categories" tag = "tags" series = "series" city = "cities" language = "languages" # Pagefind search (enabled by default). Build index after hugo: # npx pagefind --site public # [params.search] # enabled = trueSearch is shipped by the theme (
content/search/_index.md). Override that file in your site if you need a custom title or body. Disable withparams.search.enabled = false.Wire Pagefind into your build (site repo, not the theme):
JSON{ "scripts": { "build": "hugo --minify && npx pagefind --site public", "index": "npx pagefind --site public" }, "devDependencies": { "pagefind": "^1.5.0" } }{ "scripts": { "build": "hugo --minify && npx pagefind --site public", "index": "npx pagefind --site public" }, "devDependencies": { "pagefind": "^1.5.0" } }Optional Hugo mount so
hugo servercan reuse the last index:TOML[[module.mounts]] source = "public/pagefind" target = "static/pagefind" disableWatch = true[[module.mounts]] source = "public/pagefind" target = "static/pagefind" disableWatch = trueCreate content using the conventions below.
Local development against a clone
# go.mod
replace github.com/dadoonet/hugo-theme-devrel => ../hugo-theme-devrelContent conventions
Posts
content/posts/YYYY-MM-DD-slug/index.mdhugo new posts/YYYY-MM-DD-something-awesome/index.mdArchetypes fill author from site.Params.author. avatar and cover are optional: see inferred fields below. Optional series: groups posts; the post layout lists the other parts and links to /series/<term>/.
Talks
content/talks/YYYY/YYYY-MM-DD-event/index.md
content/talks/YYYY/YYYY-MM-DD-event/cover.* # optional cover imagehugo new talks/YYYY/YYYY-MM-DD-conference-name/index.mdFront matter (essentials):
title: "Talk Title"
conference:
name: "Conference Name"
city: "City"
country: "Country"
country_code: "fr" # ISO or "online"
url: "https://…" # optional
latitude: "48.856614" # optional — used by the map
longitude: "2.352222"
date: YYYY-MM-DD # event day; falls back to page `date` if omitted
authors:
- author: "Your Name" # avatar inferred; set avatar: only to override
date: YYYY-MM-DD # announcement / Hugo publish date
talk-lang: en
pdf: "YYYY/YYYY-MM-DD-event.pdf" # relative to params.talks.pdf_base_url, or site-relative if empty
talk: "Topic Name" # groups occurrences + links to template
youtube: "VIDEO_ID" # optional
links: [] # optional resources
social: [] # optional X / Bluesky / LinkedIn post URLsDrop cover: when the bundle contains cover.*. Drop avatar: when the speaker is params.author (uses params.avatar) or when a file exists at static/speakers/firstname_lastname.{avif,svg,webp,png,jpg,jpeg}. Set those fields only to use a different filename — exampleSite does this on FOSDEM (cover: hero.svg) and for Jordan Blake (avatar: speakers/jordan.svg).
date is when the talk page goes public (Hugo’s publish date). conference.date is when the session happens. Listings, maps, year grouping, the Upcoming table, and the masked permalink all use conference.date. If conference.date is omitted, the theme falls back to date.
Set date to the day you want the announcement live, and conference.date to the event. You do not need --buildFuture for upcoming talks: Hugo generates the page from date, and the theme hides the talk permalink until conference.date. Conference site links stay visible so people can still find the event. Pagefind and the home RSS wait for the event date as well; /talks/index.xml lists upcoming sessions.
--buildFuture remains optional for previewing scheduled posts (a content/posts/ page whose date is still in the future). The theme does not re-filter those posts: without the flag Hugo omits the page; with it (Netlify preview, hugo server) the post is listed and indexed like any other.
social is a list of public post URLs. The theme embeds X, Bluesky, and LinkedIn (see JavaZone in exampleSite).
Talk templates
content/talks/templates/<slug>/index.mdlayout: "template" # required
talk: "Topic Name" # must match talk: on occurrences
versions:
- label: "EN"
flag: "gb"
title: "…"
abstract: |
…hugo new talks/templates/my-talk/index.md
# then set layout: template and talk: …Satellite talk pages
Ship these _index.md files (also in exampleSite/):
| Path | Front matter | What it shows |
|---|---|---|
content/talks/all/_index.md | layout: "all" | Every talk, grouped by year, plus a map per year |
content/talks/map/_index.md | layout: "map" | One global map of all talks with coordinates |
content/talks/videos/_index.md | layout: "videos" | Talks that have youtube:, year filters, 16:9 cards |
content/talks/templates/_index.md | layout: "templates" | Recurring topics sorted by last played date |
About
content/about/index.md # shell page
content/about/10-me.md # sections sorted by filename
content/about/20-details.md
content/about/you.avif # avatar
data/socials.toml # Dream socials formatThe About layout lists socials, then each *.md section (except index.md) by name order.
Params reference
| Param | Role |
|---|---|
params.author / params.avatar | Default speaker identity (archetypes + fallbacks) |
params.talks.pdf_base_url | Prefix for talk pdf: paths; empty = local URLs |
params.search.enabled | Pagefind UI (/search + Ctrl/Cmd+K); default true |
params.navItems.talks / about_me | Dream nav entries (defaults provided) |
params.collapseNavItems | Overflow menu; default includes series with tags |
params.advanced.customCSS | Includes theme css/custom.css by default |
Taxonomies (tags, categories, series, cities, languages) must be copied into the site hugo.toml. Hugo’s built-in defaults are only tags and categories; extra taxonomies in the theme module are not applied on their own. series is listed in collapseNavItems by default and the overlay hides that nav entry when no post sets series:.
Dream coupling
Overlays assume Dream’s structure: {{ define "main" }}, dream-grid / daisyUI classes, and Dream partials (paginator.html, socials.html, commentSystemHeads.html, CSS pipeline via assets/css/output.css).
exampleSite
Fictional demo content for Alex Rivera (not a real speaker). Live at devrel.hugo.pilato.fr . For a production site using this theme, see david.pilato.fr .
The example site ships enough pages to exercise every layout:
| Kind | What is in exampleSite/ |
|---|---|
| Posts | 8 page bundles; one dated 2099 (not generated unless –buildFuture); 3-part CFP series |
| Talks | 8 sessions; FutureConf announced for 2099 (Upcoming, not indexed); FOSDEM sets cover: hero.svg |
| Templates | 3 recurring topics (Search that scales, Observability for humans, Communities that last) with EN/FR copy |
| Videos | 2 talks with YouTube ids so /talks/videos/ is not empty |
| Social | JavaZone lists public X, Bluesky, and LinkedIn URLs for the three embed types |
| About | Numbered sections (10-, 20-, 30-) plus data/socials.toml |
| Co-speaker | J on the Beach; Jordan sets avatar: speakers/jordan.svg (not firstname_lastname) |
A GitHub Actions workflow (.github/workflows/pages.yml) builds exampleSite (Hugo + Pagefind) and deploys it to GitHub Pages on every push to main. Pull requests are not built there: Netlify serves the deploy preview (netlify.toml, without --buildFuture). A separate workflow (.github/workflows/test-buildfuture.yml) builds with --buildFuture on PRs so scheduled posts stay visible in preview.
One-time Pages + DNS setup (needed because david.pilato.fr is already the custom domain of the user site dadoonet.github.io, which would otherwise redirect project URLs to a 404):
- DNS:
CNAMEdevrel.hugo.pilato.fr→dadoonet.github.io - Repo Settings → Pages → Source = GitHub Actions
- Repo Settings → Pages → Custom domain =
devrel.hugo.pilato.fr, then enable Enforce HTTPS - Confirm
exampleSite/static/CNAMEcontainsdevrel.hugo.pilato.fr(shipped in this repo)
One-time Netlify setup (this is the only exampleSite build on PRs; same pattern as david.pilato.fr
):
- In Netlify: Add new project → Import an existing project → GitHub →
dadoonet/hugo-theme-devrel - Leave build settings to
netlify.toml(command, publish directory, env) - Deploy. The first production build is skipped on purpose (
ignore = "exit 0"onmainand branch deploys). After that, each PR gets a preview URL from the Netlify GitHub App
Keeping CI tools up to date
| What | How |
|---|---|
| GitHub Actions | Dependabot (.github/dependabot.yml), weekly |
pagefind (npm) | Dependabot on exampleSite/, weekly |
| Go modules (Dream, …) | Dependabot on / and exampleSite/, weekly |
| Hugo / Go / Node pins | .github/versions.env and netlify.toml via update-tool-versions, weekly PR |
Dependabot cannot rewrite arbitrary HUGO_VERSION= strings in workflows; those pins are centralized in versions.env (and copied to netlify.toml) by the scheduled workflow above.
cd exampleSite
hugo mod tidy
hugo --minify
npx --yes pagefind --site public
bash scripts/assert-svg-utf8.sh content
bash scripts/assert-svg-utf8.sh public
bash scripts/assert-pagefind-skips-future.sh public
hugo --minify --buildFuture --destination public-buildFuture
npx --yes pagefind --site public-buildFuture
bash scripts/assert-svg-utf8.sh public-buildFuture
bash scripts/assert-pagefind-skips-future.sh public-buildFuture --buildFuture
hugo serverUse a replace in exampleSite/go.mod pointing at the parent theme while developing.
Pagefind indexes pages marked with data-pagefind-body (posts, talks, talk templates, about). Upcoming talks (event date still in the future) omit that attribute in production. Scheduled posts follow Hugo: without --buildFuture the page is not generated; with it, the post is listed and indexed. Talks no longer need --buildFuture: set date to the announcement and conference.date to the event so Hugo generates the page while listings mask the permalink until the session. --buildFuture remains optional for previewing scheduled posts. The same event-date filter is used on the homepage RSS (upcoming talks omitted there); /talks/index.xml still lists upcoming sessions. Talk templates and about pages are always indexed. Filters: section:posts, section:talks, section:templates, section:videos, section:about. Cover images (front matter, cover.*, or YouTube thumbnail) are exposed as result images. Indexed pages emit data-pagefind-sort="date:YYYY-MM-DD" (talks use the event date); empty queries (browse / filter alone) sort by date descending, while non-empty queries keep Pagefind relevance scoring. The nav shows a search icon that opens a centered modal; the section filter appears on the same row as the query once you type. Contextual presets apply on /posts* and /talks* (including templates/videos). A basic /search page remains; a richer dedicated search UI may come later.
License
MIT — see LICENSE. Includes Dream under MIT — see LICENSE-DREAM.

