Loading...
Loading...
Hugo template development skill for InfluxData docs-v2. Enforces proper build and runtime testing to catch template errors that build-only validation misses.
npx skill4agent add influxdata/docs-v2 hugo-template-devnpx hugo --quietlayouts/layouts/partials/layouts/shortcodes/npx hugo server --port 1315 2>&1 | head -50error calling partialcan't evaluate fieldtemplate: ... failedcurl -s -o /dev/null -w "%{http_code}" http://localhost:1315/PATH/TO/PAGE/mcp__claude-in-chrome__*# Navigate and screenshot
mcp__claude-in-chrome__navigate({ url: "http://localhost:1315/PATH/", tabId: ... })
mcp__claude-in-chrome__computer({ action: "screenshot", tabId: ... })
# Check for JavaScript errors
mcp__claude-in-chrome__read_console_messages({ tabId: ..., onlyErrors: true })pkill -f "hugo server --port 1315"timeout 15 npx hugo server --port 1315 2>&1 | grep -E "(error|Error|ERROR|fail|FAIL)" | head -20; pkill -f "hugo server --port 1315" 2>/dev/null{{ .Site.Data.article-data.influxdb }}{{ index .Site.Data "article-data" "influxdb" }}{{ range $articles }}
{{ .path }} {{/* Fails if item is nil or wrong type */}}
{{ end }}{{ range $articles }}
{{ if . }}
{{ with index . "path" }}
{{ . }}
{{ end }}
{{ end }}
{{ end }}{{ range $data }}
{{ .fields.menuName }}
{{ end }}{{ range $data }}
{{ if isset . "fields" }}
{{ $fields := index . "fields" }}
{{ if isset $fields "menuName" }}
{{ index $fields "menuName" }}
{{ end }}
{{ end }}
{{ end }}{{ if . }}{}{{/* This doesn't catch empty maps */}}
{{ if $data }}
{{ .field }} {{/* Still fails if $data is {} */}}
{{ end }}{{ if and $data (isset $data "field") }}
{{ index $data "field" }}
{{ end }}{{/* Build up access with nil checks at each level */}}
{{ $articleDataRoot := index .Site.Data "article-data" }}
{{ if $articleDataRoot }}
{{ $influxdbData := index $articleDataRoot "influxdb" }}
{{ if $influxdbData }}
{{ $productData := index $influxdbData $dataKey }}
{{ if $productData }}
{{ with $productData.articles }}
{{/* Safe to use . here */}}
{{ end }}
{{ end }}
{{ end }}
{{ end }}{{ range $idx, $item := $articles }}
{{/* Declare variables with defaults */}}
{{ $path := "" }}
{{ $name := "" }}
{{/* Safely extract values */}}
{{ if isset $item "path" }}
{{ $path = index $item "path" }}
{{ end }}
{{ if $path }}
{{/* Now safe to use $path */}}
{{ end }}
{{ end }}layouts/
├── _default/ # Default templates
├── partials/ # Reusable template fragments
│ └── api/ # API-specific partials
├── shortcodes/ # Content shortcodes
└── TYPE/ # Type-specific templates (api/, etc.)
└── single.html # Single page templateapi/sidebar-nav.htmlnav.html| Concern | Location | Example |
|---|---|---|
| HTML structure | | Navigation markup, tab containers |
| Data binding | | |
| Static styling | | Layout, colors, typography |
| User interaction | | Click handlers, scroll behavior |
| State management | | Active tabs, collapsed sections |
| DOM manipulation | | Show/hide, class toggling |
{{/* DON'T DO THIS */}}
<nav class="api-nav">
{{ range $articles }}
<button onclick="toggleSection('{{ .id }}')">{{ .name }}</button>
{{ end }}
</nav>
<script>
function toggleSection(id) {
document.getElementById(id).classList.toggle('is-open');
}
</script>layouts/partials/api/sidebar-nav.html<nav class="api-nav" data-component="api-nav">
{{ range $articles }}
<button class="api-nav-group-header" aria-expanded="false">
{{ .name }}
</button>
<ul class="api-nav-group-items">
{{/* items */}}
</ul>
{{ end }}
</nav>assets/js/components/api-nav.tsinterface ApiNavOptions {
component: HTMLElement;
}
export default function initApiNav({ component }: ApiNavOptions): void {
const headers = component.querySelectorAll('.api-nav-group-header');
headers.forEach((header) => {
header.addEventListener('click', () => {
const isOpen = header.classList.toggle('is-open');
header.setAttribute('aria-expanded', String(isOpen));
header.nextElementSibling?.classList.toggle('is-open', isOpen);
});
});
}main.jsimport initApiNav from './components/api-nav.js';
const componentRegistry = {
'api-nav': initApiNav,
// ... other components
};data-*<div
data-component="api-toc"
data-headings="{{ .headings | jsonify | safeHTMLAttr }}"
data-scroll-offset="80"
>
</div>interface TocOptions {
component: HTMLElement;
}
interface TocData {
headings: string[];
scrollOffset: number;
}
function parseData(component: HTMLElement): TocData {
const headingsRaw = component.dataset.headings;
const headings = headingsRaw ? JSON.parse(headingsRaw) : [];
const scrollOffset = parseInt(component.dataset.scrollOffset || '0', 10);
return { headings, scrollOffset };
}
export default function initApiToc({ component }: TocOptions): void {
const data = parseData(component);
// Use data.headings and data.scrollOffset
}{{/* Acceptable: Critical path, no logic, runs immediately */}}
<script>
document.documentElement.dataset.theme =
localStorage.getItem('theme') || 'light';
</script>assets/js/assets/js/components/main.jsdata-componentdata-*<script>npx hugo server --port 1315 --verbose 2>&1 | head -100{{/* Temporary debugging - REMOVE before committing */}}
<pre>{{ printf "%#v" $myVariable }}</pre># Verify data files exist and are valid YAML
cat data/article-data/influxdb/influxdb3-core/articles.yml | head -20.lefthook.ymlpre-commit:
commands:
hugo-template-test:
glob: "layouts/**/*.html"
run: |
timeout 20 npx hugo server --port 1315 2>&1 | grep -E "error|Error" && exit 1 || exit 0
pkill -f "hugo server --port 1315" 2>/dev/null- name: Test Hugo templates
run: |
npx hugo server --port 1315 &
sleep 10
curl -f http://localhost:1315/ || exit 1
pkill -f hugo| Action | Command |
|---|---|
| Test templates (runtime) | |
| Build only (insufficient) | |
| Check specific page | |
| Stop test server | |
| Debug data access | |
npx hugo --quietissetindexindex