|
| 1 | +# FlowFuse Website — Codebase Guide |
| 2 | + |
| 3 | +## Stack |
| 4 | + |
| 5 | +- **SSG**: Eleventy (11ty) v3, source in `src/`, output to `_site/` |
| 6 | +- **CSS**: Tailwind v3 via PostCSS → `_site/css/style.css` |
| 7 | +- **Templates**: Nunjucks (`.njk`) + Markdown |
| 8 | +- **Search**: Algolia (`scripts/index-algolia.js`) |
| 9 | +- **Hosting**: Netlify; publish dir = `_site` |
| 10 | +- **Nuxt migration**: parallel Nuxt 3 project lives in `nuxt/` (see [nuxt/CLAUDE.md](nuxt/CLAUDE.md)) |
| 11 | + |
| 12 | +## Dev commands |
| 13 | + |
| 14 | +```bash |
| 15 | +npm start # all watchers in parallel (11ty + nuxt + postcss + docs + blueprints) |
| 16 | +npm run dev # eleventy + postcss + nuxt only |
| 17 | +npm run dev:eleventy # 11ty only, port 8080 |
| 18 | +npm run dev:nuxt # Nuxt only, port 3000/3001 |
| 19 | +npm run docs # sync docs from external source once |
| 20 | +npm run build # production build |
| 21 | +``` |
| 22 | + |
| 23 | +## Directory layout |
| 24 | + |
| 25 | +``` |
| 26 | +src/ |
| 27 | +├── _data/ # Global data files (authors, tags, site config, etc.) |
| 28 | +├── _includes/ |
| 29 | +│ ├── layouts/ # Nunjucks layout templates |
| 30 | +│ └── components/ # Reusable partials |
| 31 | +├── blog/ # Blog posts → /blog/YYYY/MM/slug/ |
| 32 | +├── changelog/ # Changelog entries → /changelog/YYYY/MM/slug/ |
| 33 | +├── customer-stories/ # Case studies → /customer-stories/slug/ |
| 34 | +├── docs/ # Product docs → /docs/section/slug/ (synced from external) |
| 35 | +├── handbook/ # Employee handbook → /handbook/section/slug/ |
| 36 | +├── css/ # Tailwind + custom CSS |
| 37 | +├── images/ # Static images |
| 38 | +└── public/ # Pass-through static files |
| 39 | +scripts/ # Build-time scripts (copy_docs.js, copy_blueprints.js, etc.) |
| 40 | +lib/ # Shared helpers used by .eleventy.js and scripts |
| 41 | +.eleventy.js # Main Eleventy config (1100+ lines) |
| 42 | +``` |
| 43 | + |
| 44 | +--- |
| 45 | + |
| 46 | +## Content types |
| 47 | + |
| 48 | +### Blog posts |
| 49 | + |
| 50 | +**Source:** `src/blog/YYYY/MM/{slug}.md` |
| 51 | +**URL:** `/blog/YYYY/MM/{slug}/` |
| 52 | +**Layout:** `layouts/post.njk` |
| 53 | + |
| 54 | +```yaml |
| 55 | +--- |
| 56 | +title: "Post title" |
| 57 | +subtitle: "Optional subtitle" |
| 58 | +description: "SEO meta description" |
| 59 | +date: 2026-04-09 |
| 60 | +authors: ["username"] # must match an entry in src/_data/team/ or guests/ |
| 61 | +image: /blog/YYYY/MM/images/hero.png |
| 62 | +video: "youtube_id" # optional |
| 63 | +tags: |
| 64 | + - flowfuse |
| 65 | + - releases |
| 66 | +keywords: "kw1, kw2" # optional |
| 67 | +release: "2.29" # for release posts |
| 68 | +features: # optional release feature list |
| 69 | + - id: feature-slug |
| 70 | + heading: "Feature Name" |
| 71 | +cta: # optional call-to-action block |
| 72 | + type: sign-up # or: contact |
| 73 | + title: "CTA Title" |
| 74 | + description: "CTA body" |
| 75 | +--- |
| 76 | +``` |
| 77 | + |
| 78 | +Tag options are defined in `src/_data/blogTags.json`. Future-dated posts are excluded from collections until their date arrives. |
| 79 | + |
| 80 | +--- |
| 81 | + |
| 82 | +### Changelog entries |
| 83 | + |
| 84 | +**Source:** `src/changelog/YYYY/MM/{slug}.md` |
| 85 | +**URL:** `/changelog/YYYY/MM/{slug}/` |
| 86 | +**Layout:** `layouts/post-changelog.njk` |
| 87 | + |
| 88 | +```yaml |
| 89 | +--- |
| 90 | +title: "Feature Name" |
| 91 | +description: "One-line summary of what changed" |
| 92 | +date: 2026-04-07 12:00:00 |
| 93 | +authors: ['username'] |
| 94 | +tags: |
| 95 | + - changelog |
| 96 | +issues: # optional GitHub issue links |
| 97 | + - https://github.com/FlowFuse/flowfuse/issues/1234 |
| 98 | +--- |
| 99 | +``` |
| 100 | + |
| 101 | +Each year has a `src/changelog/YYYY/YYYY.json` that tags the collection. |
| 102 | + |
| 103 | +--- |
| 104 | + |
| 105 | +### Handbook pages |
| 106 | + |
| 107 | +**Source:** `src/handbook/{department}/{slug}.md` |
| 108 | +**URL:** `/handbook/{department}/{slug}/` |
| 109 | +**Layout:** `layouts/documentation.njk` (shared with docs) |
| 110 | + |
| 111 | +```yaml |
| 112 | +--- |
| 113 | +navTitle: "Title shown in sidebar nav" |
| 114 | +navGroup: "Optional group heading" |
| 115 | +--- |
| 116 | +``` |
| 117 | + |
| 118 | +Department folders: `company/`, `design/`, `engineering/`, `marketing/`, `operations/`, `peopleops/`, `sales/` |
| 119 | +Collection config: `src/handbook/handbook.json` |
| 120 | + |
| 121 | +--- |
| 122 | + |
| 123 | +### Product docs |
| 124 | + |
| 125 | +**Source:** `src/docs/{section}/{slug}.md` — **do not edit directly**; synced via `node scripts/copy_docs.js` from the external `flowfuse/flowfuse` monorepo. |
| 126 | +**URL:** `/docs/{section}/{slug}/` |
| 127 | +**Layout:** `layouts/documentation.njk` |
| 128 | + |
| 129 | +```yaml |
| 130 | +--- |
| 131 | +navTitle: "Page title for sidebar" |
| 132 | +navGroup: "Section heading" |
| 133 | +navOrder: 3 |
| 134 | +meta: |
| 135 | + description: "Page description" |
| 136 | +# optional redirect: |
| 137 | +redirect: |
| 138 | + to: https://example.com |
| 139 | +layout: redirect |
| 140 | +--- |
| 141 | +``` |
| 142 | + |
| 143 | +Collection config: `src/docs/docs.json` |
| 144 | + |
| 145 | +--- |
| 146 | + |
| 147 | +### Customer stories |
| 148 | + |
| 149 | +**Source:** `src/customer-stories/{slug}.md` |
| 150 | +**URL:** `/customer-stories/{slug}/` |
| 151 | +**Layout:** `layouts/story.njk` |
| 152 | + |
| 153 | +```yaml |
| 154 | +--- |
| 155 | +title: "Story title" |
| 156 | +description: "SEO meta description" |
| 157 | +image: /images/stories/hero.jpeg |
| 158 | +date: 2025-09-29 |
| 159 | +logo: /images/stories/logos/company-logo.png |
| 160 | +hubspot: |
| 161 | + formId: "uuid" |
| 162 | +story: |
| 163 | + brand: "Company Name" |
| 164 | + url: "https://company.com" |
| 165 | + logo: /images/stories/logos/company-logo.png |
| 166 | + quote: "Customer quote" |
| 167 | + challenge: "Problem statement" |
| 168 | + solution: "How FlowFuse solved it" |
| 169 | + products: |
| 170 | + - Node-RED |
| 171 | + - FlowFuse |
| 172 | + results: |
| 173 | + - Measurable outcome 1 |
| 174 | +--- |
| 175 | +``` |
| 176 | + |
| 177 | +Collection config: `src/customer-stories/customer-stories.json` |
| 178 | + |
| 179 | +--- |
| 180 | + |
| 181 | +## Global data (`src/_data/`) |
| 182 | + |
| 183 | +| File | Purpose | |
| 184 | +|------|---------| |
| 185 | +| `site.json` | Global site config (URL, name, etc.) | |
| 186 | +| `blogTags.json` | Valid blog tag values | |
| 187 | +| `team/` | Staff author profiles | |
| 188 | +| `guests/` | Guest author profiles | |
| 189 | +| `companies/` | Customer company records | |
| 190 | +| `testimonials.json` | Pull-quote testimonials | |
| 191 | +| `events.yaml` | Event calendar | |
| 192 | +| `features.json` | Product feature catalog | |
| 193 | +| `integrations.js` | Integration listings | |
| 194 | +| `eleventyComputed.js` | Dynamic computed properties | |
| 195 | + |
| 196 | +## Layouts |
| 197 | + |
| 198 | +| Layout | Used by | |
| 199 | +|--------|---------| |
| 200 | +| `layouts/base.njk` | HTML shell | |
| 201 | +| `layouts/post.njk` | Blog posts | |
| 202 | +| `layouts/post-changelog.njk` | Changelog entries | |
| 203 | +| `layouts/documentation.njk` | Docs + handbook (with sidebar nav) | |
| 204 | +| `layouts/story.njk` | Customer stories | |
| 205 | +| `layouts/nohero.njk` | General pages without hero | |
| 206 | + |
| 207 | +## Naming conventions |
| 208 | + |
| 209 | +- All slugs: **kebab-case** |
| 210 | +- Blog/changelog: folder path mirrors publish date (`YYYY/MM/`) |
| 211 | +- Images for a post live alongside it: `src/blog/YYYY/MM/images/` |
| 212 | +- Author usernames must match a file in `src/_data/team/` or `src/_data/guests/` |
0 commit comments