Hugo landing
Scaffold a production-ready Hugo landing page + blog site with Tailwind CSS, full SEO (OpenGraph, Twitter cards, JSON-LD, hreflang), configurable multilingual support, analytics, and CI/CD for GitHub Pages, Cloudflare Pages, Netlify or AWS Amplify. Use when the user wants to create a new landing page, marketing site, personal-brand site, or consultant/product site with Hugo.From its SKILL.md
npx -y skills add gerodp/hugo-landing-skill --skill hugo-landingAssembled from the repository path, not quoted from the project. Check it against their README if it does not work.
One thing to look at
- 6 stars6 stars. Stars are a popularity signal and not a quality one, but at this level it is likely that nobody has read this closely except its author, and you would be relying on your own review.
SKILL.md
10.3 KB, as published. Nobody here has run it
Hugo Landing Page + Blog Scaffolder
Creates a complete, buildable Hugo site from the embedded template/. The
result is a single-page landing site (hero, pain points, method, case studies,
about, blog preview, CTA — every section optional) plus a blog with pagination,
tags, RSS, and rich structured data.
Division of labor: the shell scripts in scripts/ handle everything
mechanical (copying, token substitution, deploy config, structure). You handle
everything requiring judgment: the interview, copywriting, translations,
branding, and fixing build warnings. Never re-implement what a script does.
Prerequisites
hugo (>= 0.146), node/npm, and git on PATH. If Hugo is missing, tell
the user to install it first (brew install hugo / see gohugo.io).
Procedure
1. Interview the user
Ask (AskUserQuestion works well; skip anything already stated):
- Site name + base URL (e.g. "Acme Consulting",
https://acme.example). If they don't have a domain yet and chose GitHub Pages, usehttps://<user>.github.io/<repo>/. - Site type: personal brand / consultant (adds a
Personentity to JSON-LD) vs product/company (Organization only). - Languages: default language + any extra languages. Any language is
supported — see
reference/multilingual.md. - Brand color + font: any Tailwind palette name or a custom color; Google Font family (default Inter).
- Homepage sections: which of pain-points, features, method/steps, stats band, case studies, testimonials, pricing, about, team, blog preview, open-source, FAQ, newsletter, final CTA to include. Hero is always on. Suggest a sensible subset for the site type (consultant: pain-points, method, case studies, about, FAQ; product: features, stats, testimonials, pricing, FAQ) rather than enabling everything.
- Blog: include one? (default yes). If no, scaffold with
--no-blog— the site then ships without any blog files. - Deploy target: github-pages (default) / cloudflare / netlify / amplify.
- Analytics: umami / plausible / ga4 / none, plus website ID and (self-hosted) script host.
- Contact CTA: scheduling link (Cal.com/Calendly) or email.
- Copy: does the user want to provide the copy themselves, or should you draft it? If they provide it, ask them to paste it (or point to a source: existing site, bio, pitch deck) — it should cover the sections selected in question 5.
2. Scaffold (deterministic)
scripts/scaffold.sh --dir <target> --name "<Site Name>" \
--url <base-url> --deploy <target> [--no-blog]
This copies the template, substitutes __SITE_NAME__ / __SITE_URL__ /
__PACKAGE_NAME__ / __HUGO_VERSION__ (Hugo version auto-resolved from the
local install or the latest GitHub release), installs the deploy target's
config, and runs git init. It refuses non-empty target directories.
With --no-blog (user said no blog in question 6) the scaffold ships no
blog files at all: blog content, archetype, blog/tag layouts, the Blog menu
entry, the homepage blog params and the blog make targets are all removed.
It also activates the tracked pre-commit hook (.githooks/pre-commit, via
git config core.hooksPath .githooks), which runs npm run lint plus a
Hugo build before every commit — the same checks as CI, so regressions
surface locally instead of in GitHub Actions. After a fresh clone of the
site's repo, that one git config command re-enables it.
3. Write the content (judgment)
Edit content/en/_index.md (or the default language's) — the homepage is
entirely front-matter driven. Remove the sections the user didn't ask for —
an absent param removes its section. See reference/content-model.md for
every param.
If the user provided copy: map it onto the selected sections' params, editing only for fit (length, front-matter structure), not voice. Then check coverage: for EVERY selected section, is there user copy for it? For each section with no matching copy, ask the user explicitly for that section's content (one AskUserQuestion round covering all gaps, naming each section and what it needs — e.g. "Pricing: tier names, prices, what's included"). Do not silently fill gaps with invented copy; only draft a gap yourself if the user answers "draft it" for that section.
If the user asked you to draft it: ask for source material (existing site, LinkedIn, pitch) rather than inventing facts, and replace ALL example copy with copy tailored to their business. Never leave the template's example copy in a delivered site.
Also update in config/_default/hugo.toml:
params.description,params.author,params.contactEmail,params.bookCallUrl,params.social.*params.jsonld.*for personal-brand sites (personName, jobTitle, knowsAbout)params.analytics.*per the interviewparams.footer.*(services list, links, copyright)- The menu entries, matching the sections that exist
Replace the example blog post (unless scaffolded with --no-blog) and legal
page with real or clearly-placeholder content per the user's preference. Keep the TOML rule from the template:
scalar params above [[array]] tables in _index.md.
4. Extra languages (script + judgment)
For each extra language:
scripts/add-language.sh --dir <target> --code <xx> --name "<Native Name>"
Then translate what the script stubbed: content/<xx>/ files,
i18n/<xx>.toml values, and the menu names in the appended config block.
Translate the i18n keys yourself — reference/multilingual.md has
ready-made TOML for common languages. hreflang + x-default are emitted
automatically once a page has translations.
Every page must exist, translated, in every language — homepage, blog
posts, legal pages, everything. No language may be missing a page and no
file may be left as an untranslated copy of the source language. verify.sh
(step 6) enforces this and fails otherwise. The same rule applies to any
content added later (e.g. a new blog post): create it in ALL languages with
a shared translationKey.
5. Branding
- Accent color: in
tailwind.config.js, setprimaryto a Tailwind palette (colors.emerald,colors.rose, …) or a custom 50–900 scale. This swaps the ACCENT color everywhere (utilities andtheme()tokens inthemes/landing/assets/css/main.css) — it does not change backgrounds or text colors. - Dark or non-light theme: component backgrounds/text/borders read the
CSS custom properties in main.css's
:rootblock, but layouts also use hardcoded light Tailwind utilities. Followreference/dark-theme.md— it has a ready-made override block and the complete utility remap list; do not rediscover the remaps by grepping. - Font: set
params.googleFontin hugo.toml (css2 family spec) AND the matching family name intailwind.config.jsfontFamily.sans. Removeparams.googleFontentirely to load no webfont (system font stack). - Favicon:
static/favicon.svgis a placeholder — update its letter/color minimum, or generate a real set (e.g. realfavicongenerator.net) and extend the icon links inthemes/landing/layouts/_partials/head.html. - Images referenced from content (
aboutPortrait,coverImage,shareImage,authorImage) live underassets/in the site root (or the theme) — create the dirs as needed.
6. Verify (deterministic + judgment)
scripts/verify.sh --dir <target> --smoke
Runs token check, translation-parity check (every page and i18n file must
exist and be translated in every language), npm install, npm run lint
(same as CI), hugo --gc --minify, and a dev-server curl against the
baseURL's path (subpath-aware for GitHub Pages project sites).
Treat any Hugo WARN/deprecation output as actionable: fix it now, so the
site is clean against the Hugo version it will build with. Then make the
initial commit yourself (verify.sh doesn't commit). After the first commit you
may enable enableGitInfo = true in hugo.toml for git-based lastmod dates.
7. Hand over
Tell the user:
-
How to run it — give the exact command for their site:
cd <target-dir> && make run # dev server at http://localhost:1313(plus
make build, andmake new-postunless--no-blog). Then ASK whether they want you to start the dev server now. If yes, runmake run(orhugo server --buildDrafts) as a background process, wait for it to be ready, and tell them the URL to open. Leave it running for them. -
Deploy setup steps for their target — see
reference/deploy.md(e.g. for GitHub Pages: push to GitHub, then Settings → Pages → Source: GitHub Actions). -
Where to customize further (content model doc, params).
-
That analytics only loads in production builds, never
hugo server.
Gotchas
_index.mdTOML: scalars must stay above[[array]]tables.- The blog section's homepage block needs
blogTitleset; the featured post (featuredPost) must be a valid page path or it's ignored. - Homepage anchors in menus must use the
/#sectionform (the menu partial makes them work under any baseURL subpath). Section ids in page order:#problems,#features,#method,#stats,#results,#testimonials,#pricing,#about,#team,#blog,#open-source,#faq,#newsletter,#contact. - The newsletter section needs BOTH
newsletterTitleandnewsletterAction(the provider's form endpoint) — never ship the example.com placeholder. - Custom analytics events: add
data-analytics-event="name"(click) ordata-analytics-view="name"(visibility) attributes — no JS needed. - GitHub Pages project sites (non-root URLs) are handled by the workflow's
-bflag; don't hardcode the repo subpath into baseURL params elsewhere. - The footer shows a "Made with Hugo Landing Skill" credit linking to the
skill's GitHub repo (translated via the
madeWithi18n key). Keep it by default; only setparams.footer.hideMadeWith = trueif the user explicitly asks to remove it.