Skip to content

industries: serve /industries/ from Nuxt - #5781

Merged
dimitrieh merged 6 commits into
mainfrom
nuxt/industries
Sep 17, 2026
Merged

dimitrieh merged 6 commits into
mainfrom
nuxt/industries

Conversation

@dimitrieh

Copy link
Copy Markdown
Contributor

Description

Moves /industries/ to Nuxt. The seven industry pages were pure frontmatter read by a Nunjucks layout, so they become a content collection plus one [slug].vue; /industries/automotive/ keeps its own page because it carries more than the others.

Shares components/use-case/Card.vue with the /use-cases/ PR, whichever lands first.

Related Issue(s)

Part of #5777

Checklist

  • I have read the contribution guidelines
  • I have considered the performance impact of these changes
  • Suitable unit/system level tests have been added and they pass
  • Documentation has been updated
  • For blog PRs, an Art Request has been created (instructions)

@netlify

netlify Bot commented Sep 11, 2026 •

Copy link
Copy Markdown

✅ Deploy Preview for flowfuse-website ready!

Name Link
🔨 Latest commit 8102050
🔍 Latest deploy log https://app.netlify.com/projects/flowfuse-website/deploys/6aabbaf30dad600008da759c
😎 Deploy Preview https://deploy-preview-5781--flowfuse-website.netlify.app
📱 Preview on mobile
Toggle QR Code...

QR Code

Use your smartphone camera to open QR code link.
Lighthouse
Lighthouse
1 paths audited
Performance: 41 (🟢 up 7 from production)
Accessibility: 95 (no change from production)
Best Practices: 92 (no change from production)
SEO: 92 (no change from production)
PWA: -
View the detailed breakdown and full score reports

To edit notification comments on pull requests, go to your Netlify project configuration.

@dimitrieh

Copy link
Copy Markdown
Contributor Author

Rebased on main, which had moved under this branch in three ways:

  • /industries/automotive/ was migrated to Nuxt on main (for the ROI calculator embed) and edited twice since. main's nuxt/pages/industries/automotive.vue is kept; this branch's independent port of the same page is dropped.
  • src/industries/automotive.njk stayed on main as a permalink: false stub so the still-11ty card grid kept its entry, and src/industries.njk gained a migratedUrl fallback to read it. Both go now: the grid is nuxt/pages/industries/index.vue and already lists automotive from its own const.
  • main already prerenders /industries/automotive; the branch's /industries/automotive/ was a duplicate.

From the review pass over the whole migration:

  • The seven [slug].vue pages were in neither sitemap: a data collection is invisible to @nuxtjs/sitemap's static-route discovery and to content-urls.get.ts (which keys on path), and they left sitemap-legacy.xml when their .md files moved. Added to sitemap.urls via the same collector the prerender list uses. /industries/automotive/ has its own .vue and is discovered normally.
  • The two <CtaContactUs ... icon /> in industry/Hero.vue rendered no icon: icon is typed string, so a value-less attribute is "", not true. Now icon="i-lucide-arrow-right", as every other call site writes it.

scripts/gen-site-art.py is now scripts/gen-site-art.mjs and nuxt/lib/rich-text.mjs is gone, both per the review on #5784.

@dimitrieh

Copy link
Copy Markdown
Contributor Author

Rebased onto main. The conflicts in nuxt/server/middleware/legacy.ts and nuxt/nuxt.config.ts are resolved and CI is green.

@Yndira-E

Copy link
Copy Markdown
Contributor

@dimitrieh could you please remove the following pages:

  1. Aerospace Components
  2. Aviation & Aerospace
  3. Electronics & Appliances
  4. Renewables
  5. Semiconductors
  6. Food & Beverage
  7. Life Sciences

@dimitrieh

Copy link
Copy Markdown
Contributor Author

@Yndira-E can you elaborate? aren't these live on FlowFuse.com. I'd expect these to be an artifact from marketing? 🤔

@Yndira-E

Copy link
Copy Markdown
Contributor

@dimitrieh the decision was made by Joyce today during the marketing meeting. I was going to create a PR myself but since you have this one open, it makes more sense to implement it here directly instead of duplicating efforts.

Comment thread nuxt/utils/siteArt.ts Outdated

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@dimitrieh same as the other PRs, please remove these siteArt files

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Removed siteArt.ts, SiteArt.vue and gen-site-art.mjs. They had no call sites at all on this branch and are not on main, so they would have landed as dead code.

Comment thread nuxt/server/middleware/legacy.ts Outdated
// 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:
// /about, /blueprints, /careers, /community, /events
// /free-consultation, /industries, /landing, /node-red, /partners, /platform,

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@dimitrieh please add this to NUXT_PREFIXES

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added /industries to NUXT_PREFIXES.

Comment thread nuxt/nuxt.config.ts
Comment on lines 287 to 307
urls: [
...blogAuthorRoutes.map(loc => ({ loc, priority: 0.6 })),
// /vs/<slug>/ is one [slug].vue over a `data` collection, so the module's
// static-route discovery cannot see it and content-urls.get.ts cannot either
// (it keys on `path`, which a data collection has no equivalent of). The three
// pages left sitemap-legacy.xml when their .njk files were deleted, so without
// this they are in neither sitemap.
...collectSlugRoutes(join(__dirname, 'content/vs'), '/vs').map(loc => ({ loc })),
],
urls: [
...blogAuthorRoutes.map(loc => ({ loc, priority: 0.6 })),
// /industries/<slug>/ is one [slug].vue over a `data` collection, so the
// module's static-route discovery cannot see it, and content-urls.get.ts cannot
// either (it keys on `path`, which a data collection has no equivalent of).
// Without this the seven pages are in neither sitemap, having left
// sitemap-legacy.xml when their .md files moved. /industries/automotive/ has its
// own .vue file, so that one is discovered normally.
...collectSlugRoutes(join(__dirname, 'content/industries'), '/industries').map(loc => ({ loc })),
],
exclude: ['/_studio/**', '/api/**'],
},

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Duplicate urls: key — sitemap already has one at line 287 for /vs/. This second one silently overwrites it in the object literal, dropping the /vs/<slug>/ entries from sitemap.xml. Should be merged into one array.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good catch, and it was live rather than cosmetic: the second urls: silently overwrote the first in the object literal, so the /vs/<slug>/ entries this PR never touched were being dropped from sitemap.xml. Merged into one array, with the shared blogAuthorRoutes spread kept once.

@@ -1,44 +1,46 @@
---
layout: layouts/industry.njk

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Remember to delete the unused layouts

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Deleted industry.njk. No consumers left once src/industries/ went, checked across the other open branches too.

Comment thread nuxt/content.config.ts Outdated
Comment on lines +412 to +415
// The .njk carried these but the layout never read them: it renders the
// shared Book-a-demo CTA, whose copy and href come from the registry.
buttonText: z.string().optional(),
buttonLink: z.string().optional(),

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

maybe drop them instead of carrying dead fields forward?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Dropped. IndustryCta only declares title and description and renders the shared Book-a-demo button, so they were never read. Gone from the schema and from the seven content files. The vsPages hero keeps its own buttonText/buttonLink, since vs/[slug].vue does read those.

Comment thread nuxt/pages/industries/index.vue Outdated
}))
// `| sort(false, true, "data.meta.title")`: case-insensitive, ascending.
return [...fromCollection, AUTOMOTIVE]
.sort((a, b) => a.title.toLowerCase().localeCompare(b.title.toLowerCase()))

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Minor: pin the locale. localeCompare(b.title.toLowerCase(), 'en') so sort order doesn't depend on the runtime's default locale (server vs. browser). Same in MatchingUseCases.vue:13.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pinned en here and in MatchingUseCases.vue:13. Worth it beyond determinism: the list is sorted once during prerender and again on hydration, so a different default collation in the browser would have been a hydration mismatch.

@dimitrieh

Copy link
Copy Markdown
Contributor Author

Pushed:

  • The sitemap block had two urls: keys. The second silently overwrote the first in the object literal, so the /vs/<slug>/ entries this PR never touched were being dropped from sitemap.xml. Merged into one array.
  • SiteArt.vue, siteArt.ts and gen-site-art.mjs removed. They had no call sites here and are not on main, so they would have landed unused.
  • cta.buttonText and cta.buttonLink dropped from the schema and the seven content files. IndustryCta only takes title and description and renders the shared Book-a-demo button, so they were dead. The vsPages hero keeps its own pair, which vs/[slug].vue does read.
  • industry.njk deleted, no callers left.
  • Both title sorts pin 'en', so prerender and hydration agree on the order.

The /industries registration is in #5815 with the seven others.

@dimitrieh

Copy link
Copy Markdown
Contributor Author

Correction to my earlier comment: #5815 is closed, the registration belongs in each migration PR instead. Pushed here.

@dimitrieh dimitrieh mentioned this pull request Sep 16, 2026
5 tasks
@dimitrieh
dimitrieh requested a review from Yndira-E September 16, 2026 09:55
@Yndira-E

Copy link
Copy Markdown
Contributor

@dimitrieh this one still hasn't been implemented #5781 (comment)

dimitrieh and others added 3 commits September 16, 2026 13:17
9 routes (the listing plus 8 industries) move off 11ty.

The seven pages on layouts/industry.njk were pure frontmatter, so they lift into
an `industries` data collection rendered by pages/industries/[slug].vue through
components/industry/. /industries/automotive/ had its own sections (testimonial
hero, metrics, applications, enterprise, compliance) and stays a hand-written
page; it reuses the one band it shared, the derived use-case grid.

Two icon paths, because the .njk had two:
- Glyphs that went through the navoption/ffIconLg wrappers keep them, via
  <NavIcon>, whose registry gained the 11 keys this content names.
- Glyphs the .njk {% include %}d raw become <SiteArt>. Those are drawn on their
  own canvases (93x80, 220x220) and take their colour from `currentColor` on the
  container, so NavIcon's fixed 24x24 wrapper would crop them and an <img> would
  lose the colour.

The problem/solution illustrations were also raw includes, but they are large
standalone diagrams that use no currentColor, so they become public images
served as <img> - which is what the layout's `[&>svg]:max-w-full` was after.

Content strings carrying <strong>, <br> and <span class="text-red-600"> went
through Nunjucks' `| safe`, which printed the whole string unescaped. They go
through nuxt/lib/rich-text.mjs now: escape everything, then re-enable exactly
those three. Anything else a content file puts in a string stays visible text.
Unit tested in nuxt/lib/rich-text.test.mjs.

UseCaseCard gained a `look` prop: industry-use-cases.njk and the /use-cases/
listing rendered the same card with different footer links, and both are kept.

Verified all 9 against production: H1s, heading outline, in-page CTA
destinations and HubSpot form mounts match, with no differences.
The sitemap block had two urls: keys. The second silently overwrote the first in
the object literal, so the /vs/<slug>/ entries this PR was not touching were
being dropped from sitemap.xml. Merged into one array.

SiteArt, its generated util and its generator script had no callers here and are
not on main, so they go rather than land unused.

cta.buttonText and cta.buttonLink were carried over from the .njk but
IndustryCta only takes title and description and renders the shared Book-a-demo
button, so they leave the schema and the seven content files with it. industry.njk
had no callers left once src/industries/ went.

The two title sorts pin 'en' so prerender and hydration agree on the order.
Without the prefix the dev middleware keeps proxying to 11ty, so npm run dev
shows the old page. The middleware returns early unless NODE_ENV is development,
which is why CI and the deploy preview both looked correct.
Marketing retired Aerospace Components, Aviation & Aerospace, Electronics &
Appliances, Renewables, Semiconductors, Food & Beverage and Life Sciences.
/industries/automotive/ stays.

All seven were live and indexed, so the URLs 301 to /industries/ rather than
404ing. Their content, the [slug].vue route that served them, the seven
components/industry/ bands, the `industries` collection and its schema, the
sitemap and prerender entries and ~40 images all go with them, since the seven
.yml files were their only consumers.

The header dropdown and the footer group lose the same seven links, so nothing
in the chrome points at a redirect.

navIcons keeps two of the keys this branch added: /vs/kepware/ and /vs/litmus/
name `arrows-pointing-out` and `link` in content merged with #5783, but neither
was ever added to the registry, so NavIcon renders nothing for them on main
today. Both pages show one icon fewer than /vs/ignition/ on live.

components/use-case/Card.vue goes back to #5780, which adds the same file and is
the only branch still using it.
@dimitrieh

Copy link
Copy Markdown
Contributor Author

Done, all seven are gone.

They were live and indexed, so the URLs 301 to /industries/ rather than 404ing. The redirects are in nuxt/redirects.ts.

Removing the content cascaded, since those seven .yml files were the only consumers of the rest: pages/industries/[slug].vue, the seven components/industry/ bands, the industries collection and its schema, the sitemap and prerender entries, and the images all go with them. /industries/automotive/ keeps its own page, and the listing is now that one card.

The header dropdown and the footer group lose the same seven links, so nothing in the chrome points at a redirect. chrome.test.mjs asserts exactly that and passes.

Two things to flag:

  • navIcons.ts keeps two of the keys this branch added. /vs/kepware/ and /vs/litmus/ name arrows-pointing-out and link in content merged with vs: serve /vs/ from Nuxt #5783, but neither was ever added to the registry, so NavIcon renders nothing and both pages are missing an icon on live. Unrelated to the retirement, but it would have disappeared along with this branch's other additions.
  • The use-case pages carry a "Common in these industries" band from their industries[] frontmatter. After this, seven of the eight chips point at a redirect. I have left the mapping alone rather than trim it, since whether that band still earns its place is a content call. use-cases: serve /use-cases/ from Nuxt #5780 owns that component if you want it changed.

@Yndira-E

Copy link
Copy Markdown
Contributor

@dimitrieh please resolve the conflicts and merge when it's ready. Thanks!

# Conflicts:
#	nuxt/content.config.ts
#	nuxt/nuxt.config.ts
#	nuxt/server/middleware/legacy.ts
#	nuxt/utils/navIcons.ts
@dimitrieh

Copy link
Copy Markdown
Contributor Author

Conflicts resolved, main is merged in.

All six of the remaining migration PRs conflicted on the same two registry files, so this is the union rather than a choice: NUXT_PREFIXES keeps both sides, the prerender list keeps both blocks, and the "still on 11ty" comment loses /industries along with /landing and /use-cases.

One thing that only appeared once main came in: #5780 brought IndustryChips.vue, which links all eight industries, seven of which this PR turns into 301s. Left as is. customer-stories/[slug].vue already links /platform/features/, a redirect with no page behind it, and the link check is green on main, so a chip pointing at a redirect passes the same way.

Holding off on merging until CI is back.

@dimitrieh
dimitrieh enabled auto-merge September 17, 2026 10:02
# Conflicts:
#	nuxt/server/middleware/legacy.ts
@dimitrieh
dimitrieh merged commit a6d5d7f into main Sep 17, 2026
8 checks passed
@dimitrieh
dimitrieh deleted the nuxt/industries branch September 17, 2026 10:10
@Yndira-E Yndira-E mentioned this pull request Sep 18, 2026
5 tasks
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants