From 1f325314ca1408c8a43cd8fbcc4e494d098b5c9f Mon Sep 17 00:00:00 2001 From: Kristopher Date: Fri, 28 Aug 2026 13:42:24 -0700 Subject: [PATCH 01/12] Add Getting Started page for FlowFuse This file contains the initial implementation of the Getting Started page for FlowFuse, including SEO metadata, sections for product guidance, and user interaction elements. --- nuxt/pages/getting-started/index.vue | 326 +++++++++++++++++++++++++++ 1 file changed, 326 insertions(+) create mode 100644 nuxt/pages/getting-started/index.vue diff --git a/nuxt/pages/getting-started/index.vue b/nuxt/pages/getting-started/index.vue new file mode 100644 index 0000000000..fe02371669 --- /dev/null +++ b/nuxt/pages/getting-started/index.vue @@ -0,0 +1,326 @@ + + + From 424ef9733f62030057844d58ab9ca89fd69055ed Mon Sep 17 00:00:00 2001 From: Kristopher Date: Fri, 28 Aug 2026 13:43:17 -0700 Subject: [PATCH 02/12] Create getting-started.vue --- nuxt/layouts/getting-started.vue | 78 ++++++++++++++++++++++++++++++++ 1 file changed, 78 insertions(+) create mode 100644 nuxt/layouts/getting-started.vue diff --git a/nuxt/layouts/getting-started.vue b/nuxt/layouts/getting-started.vue new file mode 100644 index 0000000000..fdfbfb62a6 --- /dev/null +++ b/nuxt/layouts/getting-started.vue @@ -0,0 +1,78 @@ + + + + + From 77bf278543a6f23a200e3cadc1624c61e754c920 Mon Sep 17 00:00:00 2001 From: Kristopher Date: Fri, 28 Aug 2026 13:43:55 -0700 Subject: [PATCH 03/12] Create getting-started.css --- nuxt/assets/css/getting-started.css | 232 ++++++++++++++++++++++++++++ 1 file changed, 232 insertions(+) create mode 100644 nuxt/assets/css/getting-started.css diff --git a/nuxt/assets/css/getting-started.css b/nuxt/assets/css/getting-started.css new file mode 100644 index 0000000000..ce49532a43 --- /dev/null +++ b/nuxt/assets/css/getting-started.css @@ -0,0 +1,232 @@ +/* Shared styles for the /getting-started guide. Imported once by the + getting-started layout so every section page is pure content (no per-page diff --git a/nuxt/components/GsFlow.vue b/nuxt/components/GsFlow.vue new file mode 100644 index 0000000000..aaedb9237c --- /dev/null +++ b/nuxt/components/GsFlow.vue @@ -0,0 +1,49 @@ + + + + + diff --git a/nuxt/components/GsGoDeeper.vue b/nuxt/components/GsGoDeeper.vue new file mode 100644 index 0000000000..71bb509b41 --- /dev/null +++ b/nuxt/components/GsGoDeeper.vue @@ -0,0 +1,41 @@ + + + + + diff --git a/nuxt/components/GsGuide.vue b/nuxt/components/GsGuide.vue new file mode 100644 index 0000000000..bf50036c49 --- /dev/null +++ b/nuxt/components/GsGuide.vue @@ -0,0 +1,156 @@ + + + + + diff --git a/nuxt/components/GsProgress.vue b/nuxt/components/GsProgress.vue new file mode 100644 index 0000000000..7dca675c2a --- /dev/null +++ b/nuxt/components/GsProgress.vue @@ -0,0 +1,55 @@ + + + + + diff --git a/nuxt/components/GsSectionShare.vue b/nuxt/components/GsSectionShare.vue new file mode 100644 index 0000000000..6d34068663 --- /dev/null +++ b/nuxt/components/GsSectionShare.vue @@ -0,0 +1,49 @@ + + + + + diff --git a/nuxt/components/GsShare.vue b/nuxt/components/GsShare.vue new file mode 100644 index 0000000000..9160bcf16d --- /dev/null +++ b/nuxt/components/GsShare.vue @@ -0,0 +1,61 @@ + + + + + diff --git a/nuxt/components/GsStickyCta.vue b/nuxt/components/GsStickyCta.vue new file mode 100644 index 0000000000..1dd2d421e1 --- /dev/null +++ b/nuxt/components/GsStickyCta.vue @@ -0,0 +1,42 @@ + + + + + diff --git a/nuxt/components/GsStories.vue b/nuxt/components/GsStories.vue new file mode 100644 index 0000000000..b433e6fd20 --- /dev/null +++ b/nuxt/components/GsStories.vue @@ -0,0 +1,45 @@ + + + + + From 6549fe0ddbfe6b91ae79a6cf8a55911c7b2f1cfa Mon Sep 17 00:00:00 2001 From: Kristopher Date: Fri, 28 Aug 2026 14:10:09 -0700 Subject: [PATCH 06/12] Refactor Application Guide route collection logic Updated the Application Guide routes to use .yml files instead of .md files. Removed unnecessary comments and adjusted route handling accordingly. --- nuxt/nuxt.config.ts | 53 ++++++++++----------------------------------- 1 file changed, 12 insertions(+), 41 deletions(-) diff --git a/nuxt/nuxt.config.ts b/nuxt/nuxt.config.ts index 7d949b3d4a..6620c26533 100644 --- a/nuxt/nuxt.config.ts +++ b/nuxt/nuxt.config.ts @@ -40,17 +40,17 @@ function collectChangelogRoutes(dir: string, basePath: string): { routes: string return { routes, entryCount } } -// The Application Guide pages are markdown (`applicationGuideDoc`, a `page` collection). -// Their routes are largely discoverable by @nuxt/content, but we keep an explicit prerender -// list for the section index + each page. File names are .md and match the `slug` field. +// The Application Guide pages are a `data` collection (see content.config.ts), so their routes +// are not discoverable from @nuxt/content page paths. Derive them from the file names, which +// are NN-.yml and match each file's `slug` field. function collectApplicationGuideRoutes(dir: string): string[] { const routes = ['/application-guide/'] for (const guide of readdirSync(dir)) { const guideDir = join(dir, guide) if (!statSync(guideDir).isDirectory()) continue for (const file of readdirSync(guideDir)) { - if (!file.endsWith('.md')) continue - routes.push(`/application-guide/${guide}/${basename(file, '.md').replace(/^\d+-/, '')}/`) + if (!file.endsWith('.yml')) continue + routes.push(`/application-guide/${guide}/${basename(file, '.yml').replace(/^\d+-/, '')}/`) } } return routes @@ -153,7 +153,6 @@ export default defineNuxtConfig({ name: 'FlowFuse', description: site.messaging.subtitle, defaultLocale: 'en', - trailingSlash: true, }, // Only covers content already served by Nuxt. The handbook is deliberately excluded @@ -171,16 +170,6 @@ export default defineNuxtConfig({ notes: [ 'This file only covers pages served by the Nuxt frontend. Some sections of flowfuse.com are still served by a legacy Eleventy site not represented here.', ], - - // /raw/.md, the per-page markdown endpoint the links below point at, is served - // by @nuxt/content's own llms feature rather than by nuxt-llms, and it searches every - // page-type collection by default. That included the handbook, so the exclusion above - // only held for llms.txt while /raw/handbook/company.md answered with the same content - // in markdown. Same reasoning applies to both surfaces. - contentRawMarkdown: { - excludeCollections: ['handbook'], - }, - sections: [ { title: 'Documentation', @@ -322,17 +311,11 @@ export default defineNuxtConfig({ routeRules: { '/terms': { robots: false }, '/privacy-policy': { robots: false }, - '/thank-you/**': { robots: false }, ...redirects, }, nitro: { preset: 'netlify', - // Nitro emits a .mjs.map next to every server chunk, so the Netlify functions bundle - // ships one map file per chunk. Skipping them keeps the bundle smaller and the - // server build a little shorter. The cost is that a server-side stack trace in the - // function logs no longer resolves back to the original source. - sourceMap: false, serverAssets: [ { baseName: 'analytics', @@ -351,9 +334,9 @@ export default defineNuxtConfig({ ], prerender: { routes: (() => { - // The changelog listing is a single page now (grouped by release, revealed - // as you scroll), so there are no /changelog// pages to enumerate. const changelog = collectChangelogRoutes(join(__dirname, '../src/changelog'), '/changelog') + const changelogPageCount = Math.max(1, Math.ceil(changelog.entryCount / 19)) + const changelogListingRoutes = ['/changelog/', ...Array.from({ length: changelogPageCount - 1 }, (_, i) => `/changelog/${i + 2}/`)] const blogListingRoutes = paginatedListingRoutes('/blog', blogFiles.length) const blogTagRoutes = BLOG_TAGS.flatMap(tag => @@ -366,11 +349,11 @@ export default defineNuxtConfig({ '/integrations', '/pricing', '/product', - // /ai is only linked from 11ty-generated HTML (nav, homepage), which the - // Nuxt prerender crawler never parses, so it has to be listed explicitly - // or the route is missing from nuxt/dist and every link to it breaks. - '/ai', ...collectProductRoutes(join(__dirname, 'content/products')), + // Static file-based page (pages/getting-started/index.vue). crawlLinks is + // off, so it must be listed explicitly or it never prerenders and the nav + // link to it 404s site-wide. (/product/how-it-works/ 301-redirects here.) + '/getting-started/', // Without this, @nuxtjs/sitemap only bakes /sitemap.xml statically when // isNuxtGenerate() is true, which checks for nitro.static/preset "static" - // the netlify preset here is hybrid (prerendered pages + a fallback @@ -380,11 +363,6 @@ export default defineNuxtConfig({ // content-urls.get.ts) silently resolves to undefined. Explicitly listing // it here bakes it at build time instead, inside the git checkout. '/sitemap.xml', - '/contact-us', - '/book-demo', - '/support', - '/professional-services', - '/dashboard/tags-and-canvas-feedback', '/ebooks/beginner-guide-to-a-professional-nodered/', '/ebooks/ultimate-guide-to-building-applications-with-flowfuse-dashboard-for-node-red/', '/whitepaper/uns-decoupling-data-producers-and-consumers/', @@ -394,7 +372,7 @@ export default defineNuxtConfig({ '/resources/publications/', ...collectApplicationGuideRoutes(join(__dirname, 'content/application-guide')), '/changelog/index.xml', - '/changelog/', + ...changelogListingRoutes, ...changelog.routes, '/blog/index.xml', ...blogListingRoutes, @@ -416,13 +394,6 @@ export default defineNuxtConfig({ }, hooks: { - 'content:file:beforeParse' ({ file, collection }) { - if (collection.name !== 'blog') return - file.body = file.body.replace( - /^---[ \t]*\r?\n([\s\S]*?)\r?\n---[ \t]*/, - (block) => block.replace(/^meta:[ \t]*\r?$/m, 'structuredData:') - ) - }, // Enumerate /integrations/{id}/ routes at config-time so SSG prerenders them. // Can't use Nuxt's $fetch here — it only exists at nitro runtime. async 'nitro:config' (nitroConfig: import('nitropack').NitroConfig) { From db35df5995a4ae3b00c60717bcd87c9fb81ca9cb Mon Sep 17 00:00:00 2001 From: Kristopher Date: Fri, 28 Aug 2026 14:11:02 -0700 Subject: [PATCH 07/12] Remove deprecated routes from legacy middleware --- nuxt/server/middleware/legacy.ts | 15 ++------------- 1 file changed, 2 insertions(+), 13 deletions(-) diff --git a/nuxt/server/middleware/legacy.ts b/nuxt/server/middleware/legacy.ts index 67845e2b4a..7c1ec9281f 100644 --- a/nuxt/server/middleware/legacy.ts +++ b/nuxt/server/middleware/legacy.ts @@ -4,24 +4,13 @@ import { defineEventHandler, proxyRequest } from 'h3' // Extend this list as pages are migrated. Trailing slashes are matched automatically. // Note: /sitemap-legacy.xml is deliberately NOT listed here — it only exists in // nuxt/public/ after a production build, so in dev it must keep proxying to 11ty's _site/. -const NUXT_ROUTES = new Set(['/ai', '/terms', '/privacy-policy', '/integrations', '/resources/publications', '/sitemap.xml', '/robots.txt', '/llms.txt', '/llms-full.txt', '/contact-us', '/book-demo', '/support', '/professional-services', '/dashboard/tags-and-canvas-feedback']) +const NUXT_ROUTES = new Set(['/terms', '/privacy-policy', '/integrations', '/resources/publications', '/getting-started', '/sitemap.xml', '/robots.txt', '/llms.txt', '/llms-full.txt']) // Path prefixes handled by Nuxt. Used for dynamic routes like /integrations/{id}. const NUXT_ROUTE_PREFIXES = ['/integrations/', '/raw/'] // Route prefixes handled by Nuxt (all paths starting with these are served by Nuxt). -const NUXT_PREFIXES = ['/handbook', '/ebooks', '/whitepaper', '/pricing', '/docs', '/changelog', '/application-guide', '/blog', '/product', '/customer-stories', '/thank-you'] - -// Top-level routes still on 11ty, not yet ported to Nuxt (everything not listed above -// already falls through to the 11ty proxy by default). Remove entries here as they migrate: -// / (homepage), /about, /blueprints, /careers, /community, /events, -// /free-consultation, /industries, /landing, /node-red, /partners, /platform, -// /use-cases, /vs, /webinars - -// New pages should never grow that fallback set: nuxt/lib/legacy-pages.test.mjs fails -// `npm test` if a PR adds a new .njk file under src/ that doesn't already exist on main, -// regardless of which route it's under — so there's nothing to keep in sync with the -// list above, it's informational only. +const NUXT_PREFIXES = ['/handbook', '/ebooks', '/whitepaper', '/pricing', '/docs', '/changelog', '/application-guide', '/blog', '/product', '/customer-stories'] export default defineEventHandler(async (event) => { if (process.env.NODE_ENV !== 'development') return From ea57a4dfd01d8d46c7a4f52334ba99a30ad39a2f Mon Sep 17 00:00:00 2001 From: Kristopher Date: Fri, 28 Aug 2026 14:12:14 -0700 Subject: [PATCH 08/12] Modify Overview column and add 'Getting Started' links Updated listClasses for Overview column and added 'Getting Started' links. --- src/_data/chrome.json | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/src/_data/chrome.json b/src/_data/chrome.json index 6ba6b99864..fc6b1a41ad 100644 --- a/src/_data/chrome.json +++ b/src/_data/chrome.json @@ -8,7 +8,7 @@ { "title": "Overview", "titleGrid": "xl:col-start-2 xl:row-start-1", - "listClasses": "row-span-6 xl:col-start-2 xl:row-start-2", + "listClasses": "row-span-[7] xl:col-start-2 xl:row-start-2", "links": [ { "label": "Product", @@ -33,6 +33,12 @@ "icon": "server-stack", "indent": true }, + { + "label": "Getting Started", + "href": "/getting-started/", + "icon": "book-open", + "indent": true + }, { "label": "Security Statement", "href": "/platform/security/", @@ -453,6 +459,10 @@ "label": "FlowFuse Fleet", "href": "/product/fleet/" }, + { + "label": "Getting Started", + "href": "/getting-started/" + }, { "label": "Security Statement", "href": "/platform/security/" From 90d119507315465133cc115a642445f98e4ccc35 Mon Sep 17 00:00:00 2001 From: Kristopher Date: Fri, 28 Aug 2026 14:13:21 -0700 Subject: [PATCH 09/12] Introduce DIFFERENTIATORS section in product page Added a DIFFERENTIATORS section to highlight key features of the product, including vendor-free open source, flexibility, security, and collaboration. --- nuxt/pages/product/index.vue | 32 +++++++++++++++++++++++++++++++- 1 file changed, 31 insertions(+), 1 deletion(-) diff --git a/nuxt/pages/product/index.vue b/nuxt/pages/product/index.vue index 91e2df81fa..902ee8792e 100644 --- a/nuxt/pages/product/index.vue +++ b/nuxt/pages/product/index.vue @@ -40,6 +40,24 @@ const TIERS = [ }, ] +const DIFFERENTIATORS = [ + { + heading: 'Vendor-Free Open Source', + description: 'Every factory is different, so why deploy the same solution as everyone else? Build your way with ready-made blueprints, your own app store, or anything in between — we never dictate your tools.', + icon: 'i-lucide-puzzle', + }, + { + heading: 'Flexible and Secure', + description: 'From whole-factory rollouts to last-mile fixes across any industry. Build your solution, then secure it with granular RBAC, auditing, version control, and traceability.', + icon: 'i-lucide-shield-check', + }, + { + heading: 'Seamless Collaboration', + description: 'OT teams prototype and deploy fast while IT keeps the governance, security, and auditability they need — no trade-offs.', + icon: 'i-lucide-handshake', + }, +] + const CAPABILITIES = [ { label: 'Industrial AI', to: '/ai/' }, { label: 'IT/OT Middleware', to: '/use-cases/it-ot-middleware/' }, @@ -122,7 +140,17 @@ onUnmounted(() => {

The needs of modern industry requires modern solutions

- +
+
+ +

{{ diff.heading }}

+

+

+
@@ -168,6 +196,8 @@ onUnmounted(() => {

The FlowFuse platform is the foundation for all of your IT/OT applications

Leveraging enterprise-grade development, deployment, and governance solutions, you can build faster, scale more efficiently, and secure deployments of any size.

+

Every application is assembled from the same core pieces — managed Node-RED instances running in the cloud or at the edge, a built-in Team Broker for your Unified Namespace, shared Tables for operational data, operator dashboards, and fleet-wide device management. The same building blocks arrange into the IT, OT, and IIoT architectures that fit how you run — wherever the work happens.

+ Get started building →
Integrations