惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

小众软件
小众软件
Security Archives - TechRepublic
Security Archives - TechRepublic
Security Latest
Security Latest
K
Kaspersky official blog
P
Palo Alto Networks Blog
T
Threat Research - Cisco Blogs
D
Darknet – Hacking Tools, Hacker News & Cyber Security
T
Tor Project blog
Threat Intelligence Blog | Flashpoint
Threat Intelligence Blog | Flashpoint
A
Arctic Wolf
NISL@THU
NISL@THU
GbyAI
GbyAI
腾讯CDC
罗磊的独立博客
Simon Willison's Weblog
Simon Willison's Weblog
Scott Helme
Scott Helme
S
SegmentFault 最新的问题
Cyberwarzone
Cyberwarzone
Jina AI
Jina AI
S
Schneier on Security
WordPress大学
WordPress大学
云风的 BLOG
云风的 BLOG
博客园 - 【当耐特】
Cyber Security Advisories - MS-ISAC
Cyber Security Advisories - MS-ISAC
Spread Privacy
Spread Privacy
C
CXSECURITY Database RSS Feed - CXSecurity.com
让小产品的独立变现更简单 - ezindie.com
让小产品的独立变现更简单 - ezindie.com
The Register - Security
The Register - Security
C
Cybersecurity and Infrastructure Security Agency CISA
L
Lohrmann on Cybersecurity
V
Vulnerabilities – Threatpost
G
GRAHAM CLULEY
P
Proofpoint News Feed
Cisco Talos Blog
Cisco Talos Blog
博客园_首页
雷峰网
雷峰网
量子位
酷 壳 – CoolShell
酷 壳 – CoolShell
V2EX - 技术
V2EX - 技术
O
OpenAI News
博客园 - 司徒正美
S
Security @ Cisco Blogs
Last Week in AI
Last Week in AI
M
MIT News - Artificial intelligence
博客园 - 叶小钗
Vercel News
Vercel News
AI
AI
博客园 - 聂微东
Webroot Blog
Webroot Blog
J
Java Code Geeks

CloudCannon Blog

Building with AI: Git-based vs headless vs traditional CMS CloudCannon + Astro: performance meets powerful content management Introducing the Astro Component Starter Introducing Jetstream — built on the Astro Component Starter Why we switched to the system font stack Redesigning CloudCannon’s docs with Diátaxis, Lume, and Pagefind Make content editing more visual: upgraded Editable Regions How Configuration Mode makes building editing interfaces easy Your hosting just got an upgrade (and a price cut) Custom testing domains for professional branding Keep your content consistent with input validation Managing multilingual content in CloudCannon Simplify team publishing with conflict resolution and domain tools Open Beta: Publishing Conflict Resolution Getting started with CloudCannon and Astro: Bookshop, components, and live editing Welcome to the CloudCannon Community! Omnichannel delivery is just marketing spin from API-based CMS companies Getting started with CloudCannon and Astro: Snippets and Collections Managing digital assets in CloudCannon: a guide to smart asset storage Understanding CloudCannon's branching workflows and Projects: a complete guide What is a static website? CloudCannon’s 2024 wrapped Getting started with CloudCannon and Astro: WYSIWYG blogging Jamstack vs. WordPress: reasons to make the change The top five static site generators for 2025 (and when to use them!) Free Jekyll themes for 2025: ten great community options Eleventy (11ty) vs. Hugo How to set up WYSIWYG editing with MkDocs Material The rise of static-first websites: why major brands are making the switch Watching your Core Web Vitals on Jamstack Understanding the difference between static, dynamic, and hybrid websites Looking for an alternative to Netlify CMS or Decap CMS? Designing components for your website editors: a CloudCannon case study Does my website look big in this? Six tips to lower your page weight Content is sacred — so own your revision history The eternal balancing act: load time vs. delay time Streamlined Headless Mode, Unified Configuration, and live data editing What is a headless CMS? Looking for a TinaCMS or Tina Cloud alternative? The ultimate guide to Hugo Sections Coming soon: Live config editing and data reloading Faster publishing workflows out now! Why information architecture matters for your website Website UX vs SEO: picking your battles Easily manage your multilingual Astro site in CloudCannon How you can optimize publishing workflows for your content team How you can optimize your CMS for SEO success How you can optimize your Content Editor for long-form articles How you can optimize your Visual Editor for page building Secure, swift, and stable: static sites for the financial sector Enhanced flexibility for teams with Custom Permissions Building static sites that scale The Inaugural 11ty International Symposium on Making Web Sites Real Good How to manage hundreds of connected websites with a Git-based headless CMS How we’re building CloudCannon for accessibility CloudCannon’s new editing improvements are here! Open Beta | New ways to collaborate on editing your websites Top 11 free Eleventy themes for 2024 Top 10 free Astro themes to use in 2024 Why choose a Git-based headless CMS over a monolithic DXP in 2024? Learning web development: a self-guided roadmap Partner Site of the Month: Blüthner Piano Centre, by Winteractive CloudCannon’s 2023 wrapup Let’s create a microblog with visual editing using Bookshop and Eleventy Update and visualize your branches with CloudCannon Projects What is a Git-based CMS and why you should use one CloudCannon secures SOC 2 certification The complete guide to growing your web development agency Automatically optimize your images with Eleventy Image and CloudCannon Share components and syndicate content with Site Mounting Partner Site of the Month: Cru Uncorked, by Ed Meehan New web component for responsive HTML tables Wrapping up HugoConf 2023 Partner Site of the Month: Van Dillen Antieke Bouwmaterialen, by Fulldev How to become a freelance web developer: a comprehensive guide Q3 2023: CloudCannon features and improvements 22 ways to deliver more value to your web development clients Partner Site of the Month: DC Gay Flag Football League, by Ed Cupaioli A new way to configure your CloudCannon sites CloudCannon — the official CMS partner of Eleventy Full CloudCannon support for Nuxt static sites Partner Site of the Month: Stadium Bike, by Insight Creative, Inc. HugoConf 2023, brought to you by CloudCannon DAM Support for Cloudflare R2 and DigitalOcean Spaces Eleventy (11ty) vs. Gatsby in 2023 – which SSG is best for you? How CloudCannon’s live editing works with Astro and Bookshop Partner Site of the Month: Fiducian, by Croissant & Baguette Eleventy (11ty) vs. Astro How to customize CloudCannon’s Client Sharing interface Let anyone, anywhere, edit your CloudCannon sites Top 23 free Astro themes for building out-of-this-world static sites in 2023 How Jampack optimizes our Eleventy website and improves performance Astro vs. Next.js CloudCannon.com is now built with Eleventy! Out-of-this-world support for all Astro users Introducing the CloudCannon Partner Program Full CloudCannon support for Gatsby Top 10 Free SvelteKit Themes for Building Lighting-Fast Static Sites in 2023 Enhanced CloudCannon support for Next.js users Upcoming CloudCannon Interface improvements (Open Beta)
Deconstruction: How Bootstrap powers their documentation site with Hugo
2021-10-11 · via CloudCannon Blog

Bootstrap is used to rapidly develop consistent, responsive websites. It was initially created at Twitter as a way to encourage design consistency in internal tools. It was publicly released in August 2011, and has since become one of the most popular projects on GitHub.

The most important part of a documentation site is the content, so let's start there. The front matter for content pages is simple, using five variables for the most part:

---
layout: docs
title: Accessibility
description: A brief overview of Bootstrap's features and limitations for the creation of accessible content.
group: getting-started
toc: true

---

layout, title and description are self-explanatory. Things get interesting with group and toc, which are used to build navigation menus. We'll look at how navigation works in the next section. For the body content, Bootstrap does a great job of using simple markdown. The content is a mix of headings, paragraphs, code blocks, lists, links, and the occasional HTML block for tables and accessibility.

### Visually hidden content

Content which should be visually hidden, but remain accessible to assistive technologies such as screen readers, can be styled using the `.visually-hidden` class. This can be useful in situations where additional visual information or cues (such as meaning denoted through the use of color) need to also be conveyed to non-visual users.

<p class="text-danger">
<span class="visually-hidden">Danger: </span>
This action is not reversible
</p>

Keeping the markdown simple is excellent because it makes it easier for the core team to maintain their documentation and for others to contribute. It also helps keep consistency throughout the site.

The Bootstrap documentation has three levels of navigation:

Bootstrap top navigation

This is the primary navigation for the Bootstrap site and also the most simple in terms of implementation. Each link is hard coded and has an if statement to trigger the active state:

<li class="nav-item col-6 col-md-auto">
<a class="nav-link p-2{{ if .IsHome }} active" aria-current="page{{ end }}" href="/" onclick="ga('send', 'event', 'Navbar', 'Community links', 'Bootstrap');">Home</a>
</li>
<li class="nav-item col-6 col-md-auto">
<a class="nav-link p-2{{ if eq .Page.Layout 'docs' }} active" aria-current="true{{ end }}" href="/docs/{{ .Site.Params.docs_version }}/getting-started/introduction/" onclick="ga('send', 'event', 'Navbar', 'Community links', 'Docs');">Docs</a>
</li>
<li class="nav-item col-6 col-md-auto">
<a class="nav-link p-2{{ if eq .Page.Title 'Examples' }} active" aria-current="true{{ end }}" href="/docs/{{ .Site.Params.docs_version }}/examples/" onclick="ga('send', 'event', 'Navbar', 'Community links', 'Examples');">Examples</a>
</li>
Bootstrap side navigation

The sidebar is more tricky. It's a two-level navigation component used on documentation pages. The first level is the group title, a high-level concept with an arrow indicating it can be expanded. Clicking on the group title expands the sidebar to show the navigation items related to the group. The sidebar is populated from a YAML data file:

- title: Getting started
pages:
- title: Introduction
- title: Download
- title: Contents
- title: Browsers & devices
- title: JavaScript
- title: Build tools
- title: Webpack
- title: Accessibility
- title: RFS
- title: RTL

- title: Customize
pages:
- title: Overview
- title: Sass
- title: Options
- title: Color
- title: Components
- title: CSS variables
- title: Optimize

Using a data file here makes sense for a couple of reasons:

  1. Ordering: Arrays give you an intuitive way of ordering things. The other common method of handling order is to have a 'page-weight' number in the front matter of each document. When you're outputting documents, you would order by the page weight. The advantage of page weight is you don't need a separate YAML file to set an order. The downside is reordering can get messy. In this case, I think the Bootstrap team has picked the correct implementation.
  2. Naming: The navigation group and item titles have to live somewhere. Having them in the YAML file works. To render the sidebar, Bootstrap has a docs-sidebar.html partial. They start by iterating over the data file:
{{- range $group := .Site.Data.sidebar -}}
{{- $group_slug := $group.title | urlize -}}
{{- $is_active_group := eq $.Page.Params.group $group_slug -}}

$group_slug is calculated by slugifying the title in the data file. $group_slug is then compared to the group front matter field (The one we saw in the content section ) on the current page being rendered. If they match, then the current page is part of this navigation group. This state is stored in the $is_active_group variable. The next code block renders the group title. The interesting part from a Jamstack perspective is seeing how they use $is_active_group. It's used to add a collapsed class if it's not the active group, it's also used to set two accessibility attributes: aria-expanded and aria-current.

<li class="mb-1">
<button class="btn d-inline-flex align-items-center rounded {{ if not $is_active_group }} collapsed {{ end }}" data-bs-toggle="collapse" data-bs-target="#{{ $group_slug }}-collapse" aria-expanded="{{ $is_active_group }}"{{ if $is_active_group }} aria-current="true"{{ end }}>
{{ $group.title }}
</button>

Following the group title, we have the logic to render the navigation items. First, the pages in the current group are iterated over. Each page is assigned to $doc. Then $doc's title is slugified and checked to see if it's the current page in a similar way to how the active group was calculated. The result of this is assigned to $is_active. $href is a combination of the docs_version, $group_slug and $doc_slug. It's a bit magic because there just happens to be a page with that URL. There's no real connection there from Hugo's point of view. This will work as long the team is consistent with their structure and doesn't override any permalinks. As an improvement, you could find the source page using the $group_slug and $doc_slug and get the permalink from there. This would be less fragile as you are free to change the permalink. However, it would add extra code and complexity, so it may not be necessary here.

<div class="collapse{{ if $is_active_group }} show{{ end }}" id="{{ $group_slug }}-collapse">
<ul class="list-unstyled fw-normal pb-1 small">
{{- range $doc := $group.pages -}}
{{- $doc_slug := $doc.title | urlize -}}
{{- $is_active := and $is_active_group (eq $page_slug $doc_slug) -}}
{{- $href := printf "/docs/%s/%s/%s/" $.Site.Params.docs_version $group_slug $doc_slug -}}
<li><a href="{{ $href }}" class="d-inline-flex align-items-center rounded {{ if $is_active }} active {{ end }}"{{ if $is_active }} aria-current="page"{{ end }}>{{ $doc.title }}</a></li>
{{- end -}}
</ul>
</div>
</li>

One point worth noting is while it looks easy to update a navigation group or item title in the YAML file, there's more than meets the eye. The titles have urlize run on them to link them back to the original document as we saw. If you want to update a title for the navigation, you will also have to change the file name of the source file. Otherwise the link will break. As a small improvement, I would opt to have the slugified references in the YAML file and then have an optional front matter field in each document called menu_title if you want to use something other than title for menu items.

Table of contents Direct link to this section

Bootstrap documentation table of contents

On the right side of the most of their documentation pages, Bootstrap includes a table of contents which lists the headings on the page. It's a nice way to help users find the content they're looking for. Hugo makes generating a table of contents super straight forward, it parses the markdown and automatically makes the HTML output available with the variable .TableOfContents. As we saw before, some pages have a toc front matter variable. The Bootstrap site uses this variable to determine if the table of contents should be rendered:

{{ if (eq .Page.Params.toc true) }}
<div class="bd-toc mt-4 mb-5 my-md-0 ps-xl-3 mb-lg-5 text-muted">
<strong class="d-block h6 my-2 pb-2 border-bottom">On this page</strong>
{{ .TableOfContents }}
</div>
{{ end }}

Versioning Direct link to this section

Successful software frameworks like Bootstrap release many major versions of their software. Each major version likely differs enough from each other to warrant its own documentation. Bootstrap has a clever dropdown that makes it easy to switch between versions.

Bootstrap dropdown showing versioning

Clicking All versions takes you to a page with the documentation for every major and minor release going back to version 1.0 which is pretty neat. Bootstrap has an elegant way of handling this legacy content. doc-versions.yml is a data file that lists all the available versions:

# Snippet of docs-versions.yml

- group: v3.x
baseurl: "https://getbootstrap.com/docs"
description: "Every minor and patch release from v3 is listed below. Last update was v3.4.1."
versions:
- v: "3.3"
- v: "3.4"

- group: v4.x
baseurl: "https://getbootstrap.com/docs"
description: "Current major release and its minor versions. Last update was v4.6.0."
versions:
- v: "4.0"
- v: "4.1"
- v: "4.2"
- v: "4.3"
- v: "4.4"
- v: "4.5"
- v: "4.6"

This file is iterated over on the versions page to create the version appendix. Each major and minor version of Bootstrap has its own branch (or tag), which contains only that version's documentation. The URL structure has a segment for the version number e.g. /docs/5.0/getting-started/introduction/. I don't have visibility into how this is deployed but what I suspect is that each site is generated independently, and then combined after the build. CloudCannon has a feature called subpaths that makes it easy to do something similar - mount multiple sites together to look like a single site.

I'm a big fan of this strategy for documentation versioning for several reasons:

  1. Bootstrap has 29 versions of its documentation. With each major version, Bootstrap is refining the design of their docs to keep them fresh and easy to use. In some cases, they've completely changed how the documentation is generated. In the early days, the documentation was a simple static website, then they switched to Jekyll and more recently Hugo. If they had to maintain a single design and platform for 29 versions, it would be hard to make any changes without breaking something. Plus, it's cool seeing the design evolve through the years.
  2. Between versions the documentation is very similar. If all versions were on the same branch, it would be easy to get confused about which of the 29 introduction pages I was meant to update. The way Bootstrap has it set up, as long as I'm on the right branch, I know I'm editing the right docs.
  3. Finally, the Bootstrap source code and documentation are in the same repository. This makes it easy to keep documentation in sync with development. If all versions were available on every branch and I spot a spelling mistake on an older version, do I make the change on the latest branch or the branch for that version? This would get messy quickly.

Search is a great way to help users quickly find what they're looking for. There are several ways to add search to a static site. We recommend using a tool like Pagefind, which can be used alongside any static site generator, while respecting users' bandwidth.

Bootstrap uses Algolia, which is hard to beat when it comes to ease of integration and search performance.

search query in bootstraps search box

Algolia has a free tool built explicitly for documentation called Docsearch. The way it works is simple:

  1. You enter the URL of your documentation site on the Docsearch site.
  2. Algolia automatically scrapes and indexes your content every day.
  3. Algolia gives you a JavaScript snippet to embed on your site. This snippet turns an input box into a fancy autocomplete search box with your content. That's all there is to it!

Wrapping up Direct link to this section

Bootstrap is a fantastic example of a well-built documentation site on Hugo. If you're building a documentation site, I'd recommend having a dig around their source code as they typically have elegant solutions to common problems.

  • New to Hugo? Take a look at this Hugo tutorial to get you started.

  • If you have an existing Bootstrap site you'd like to bring over to Hugo with live editing and page-building enabled by our open-source tool Bookshop, we have a Bootstrap-to-Hugo tutorial to help you learn the ropes there too! (And if you'd rather read in your own time, we've written a range of Bookshop guides for Astro, SvelteKit, Eleventy, Hugo, and Jekyll.)