Marketing Website
- All written content should be in US English.
- All page titles should summarise the content, keep the URL length as short as practical and use Kebab Case.
- All images should use informative alt tags which clearly describe the point of an image rather than all the details. Alt tags should be no longer than 60 characters.
- When mentioning FlowFuse Concepts (terminology) where possible we should link to an explanation of that concept.
- All written content should use the Oxford Comma. We believe the Oxford Comma reduces the ambiguity of written technical content.
Events Banner
The event banner at the top of the website can display more than one event or announcement.
To add or update an event, you'll need to modify the following file. The information should be formatted as follows for each banner:
- type: "Webinar"
title: "Deploy FlowFuse on Industrial IoT with NCD.io"
buttonText: "Learn more"
link: "/webinars/2024/deploy-flowfuse-on-industrial-iot-with-ncd-io/"
expire: "2024-05-29T16:00:00Z"
The expire field is used to set the date and time when the event should stop being displayed on the banner. The date and time are set in the ISO 8601 format: YYYY-MM-DDTHH:MM:SSZ, and the time is in Coordinated Universal Time (UTC).
For example, expire: "2024-05-29T15:00:00Z" means that the event will stop being displayed on the banner at 16:00 UTC on May 29, 2024.
Please note that the website is built once a day at 9:30 AM UTC, and also on Wednesdays and Thursdays at 16:05 UTC. This means that if an event expires at some point during the day, it will still be displayed until the next time the website is built.
If there were more than one event, then duplicating that and updating the info will create the second banner for rotation. It would look like this:
- type: "Webinar"
title: "Deploy FlowFuse on Industrial IoT with NCD.io"
buttonText: "Learn more"
link: "/webinars/2024/deploy-flowfuse-on-industrial-iot-with-ncd-io/"
expire: "2024-05-29T15:00:00Z"
- type: "New Release"
title: "FlowFuse 2.4: making it easier to work with Snapshots, Blueprints & Devices "
buttonText: "See blog post"
link: "/blog/2024/05/flowfuse-2-4-release/"
expire: "2024-05-20T15:00:00Z"
If there is only one event, the banner will continuously display that event. If there are multiple events, the banner will rotate through them, displaying each one for a few seconds at a time.
Images
All images on the website, whether part of the blog or otherwise, are run though an image pipeline, that compresses, resizes and converts the images to reduce file size and improve page loading speed.
That pipeline also generates x2 versions of images for high DPI screens if the provided image is large enough.
The first build locally will take roughly a minute, while the cache remains intact, all future builds should take only a few seconds.
Guidelines for including images:
- Downsize the image to at maximum two times the width it will be displayed (1300px for blog prose)
- Ideally use JPEG for lossy ok images, and PNG for others (they will be converted to AVIF and WebP regardless)
- Wherever possible use:
- The markdown image include tag in blog prose:
- You can use the
@skiptag to disable the image pipeline entirely for an image
- You can use the
- The MDC image component in page body:
<NuxtImg src="./relative-path-to-image.png" alt="Image alt tag for screen readers" width="150" />- Where
widthis the maximum width the image will be displayed on the page (source image should be two times this width)
- Where
- The markdown image include tag in blog prose:
- GIFs can grow incredibly large, consider using a video in a modern format if the animation is longer than a few seconds — a silent looping WebM (see Embedding Videos in Article Body) works well for short screen-recording style animations, while YouTube is better suited to longer or narrated videos
Non-Image Assets
If you need to include things other than images, e.g. Video or resource bundles (zip files), these should be uploaded to the website-data S3 bucket in the Production AWS account. The exceptions are YouTube videos and short WebM clips (see below) — both are embedded without an S3 upload.
This can be done by Ben, Nick, Piotr or ZJ.
A URL will be made available to include in the post.
YouTube Videos
YouTube videos can be embedded directly in pages using the <lite-youtube> web component — no S3 upload needed:
<lite-youtube videoid="VIDEO_ID" params="rel=0" style="width: 704px; height: 100%;" title="Video title"></lite-youtube>
Do not use raw
<iframe>tags to embed YouTube videos. Iframes load YouTube's scripts and set tracking cookies on page load, before any user consent, which is not GDPR-compliant. The<lite-youtube>component only loads the YouTube player when the user explicitly clicks play. See Embedding Videos in Article Body for more detail.
WebM Videos
For short, silent, looping animations that would otherwise be a GIF, check a .webm file into the post's own images/ folder (same as any other image asset — no S3 upload needed) and embed it directly:
<video autoplay loop muted playsinline aria-label="Description of what the video shows" width="1280" height="720" preload="none"><source src="./images/example.webm" type="video/webm" /></video>
width/height should match the WebM's actual pixel dimensions so the browser can reserve layout space before the video loads, and preload="none" stops it from buffering until it scrolls into view. See Embedding Videos in Article Body for more detail.
Meta Keywords
Meta keywords are a type of HTML metadata that describe the topics covered by a page. Major search engines (Google, Bing) have not used meta keywords as a ranking signal since 2009 — setting them has no effect on search visibility or rankings.
A few pages still set them, in the page's own .vue file (keywords in its useSeoMeta call), and they may be used by site-search tools (such as Algolia) or other non-Google indexers, but they should not be treated as an SEO lever. A keywords or meta.keywords field in a markdown page's front matter is not read.
Call-to-Action Buttons
The site has five main call-to-action destinations, each with fixed copy — you cannot write new button text for these, only choose how the button looks and where it sits on the page:
| Component | Goes to | Button text |
|---|---|---|
<CtaSignUp> | app.flowfuse.com/account/create | "Free Trial" (nav) or "Try it out" (everywhere else) |
<CtaSignIn> | app.flowfuse.com | "Sign In" |
<CtaContactUs> | /contact-us/ | "Contact Us" |
<CtaBookDemo> | /book-demo/ | "Book a Demo" |
<CtaPricing> | /pricing/ | "View Pricing" |
Contact Us vs. Book a Demo: these two CTAs carry different intent signals and should be treated accordingly. Contact Us is for general inquiries — support questions, partnership asks, anything that isn't a sales-ready request. Book a Demo is a high-intent motion: someone clicking it has effectively raised their hand to buy, so the path from click to booked call should have as little friction as possible. This is why Book a Demo needs to be visible from the main nav and not buried.
If a page needs different wording than what's listed above, that's a sign the destination needs a sixth CTA, not a new prop on these five or custom inline code.
They are available on every page: in .vue pages under nuxt/pages/, and in markdown under nuxt/content/.
Choosing a style
Every component takes the same variant prop, which controls the look. Click "Show code" under any example to see (and copy) the exact syntax:
There's also a nav-text variant — the plain, no-underline treatment used for "Free Trial" (main nav) and "Sign In" (utility bar). It's not meant for general use in page content, so it's not in this gallery; stick to the five variants above for anything outside the nav.
There's a sixth variant, ghost, for a button that reads like a solid one (bold, uppercase, same padding) but has no background or border. Because it has no background of its own, it needs a color to know what text color to use, and an icon to add a trailing icon:
The dark card above is just to make the white text visible in this doc, use color="white" on an actual dark background (like the homepage hero photo), and color="primary" or color="highlight" on a light one.
Props reference
| Prop | Required | What it does |
|---|---|---|
variant | Yes | Visual style: primary, primary-outlined, highlight, highlight-outlined, or ghost — plus nav-text, reserved for the main nav/utility bar |
position | Yes | Where the button sits on the page (e.g. hero, pricing-card, footer) — shows up in analytics, so use a short, descriptive label |
plan | No | Which pricing plan the button belongs to, if relevant (e.g. edge, hub, fleet) — also shows up in analytics |
color | No | Only for variant="ghost": primary, highlight, or white — which text color to use, since a ghost button has no background to imply one |
icon | No | An icon name to show after the button text, e.g. i-lucide-arrow-right |
One-off links (CtaCustom)
Occasionally a page needs a button-styled link that genuinely isn't one of the five destinations above — a link to a HubSpot meeting scheduler, an external community forum, and so on. There's a CtaCustom component for that, with its own copy and destination, still using the same look and tracking system as everything else on this page.
Using it takes two steps:
- Register the destination in
nuxt/lib/custom-cta-destinations.ts— a short list of entries, each just a name, a URL, and a PostHog event name (no coding beyond typing those three things in). This is what keeps the same URL from accidentally getting tracked under two different event names if it's ever linked from more than one place. - Reference that name from
<CtaCustom>in the page, along with the button's copy and style — same as any otherCta*component.
Both steps are plain text edits — no different in spirit from adding a new entry to events.yaml or a new industry page's frontmatter.
Example. The support page's link to the Node-RED community forum works like this. First, an entry in nuxt/lib/custom-cta-destinations.ts:
communityForum: {
href: 'https://discourse.nodered.org/c/vendors/flowfuse/24/',
event: 'cta-community-forum',
},
Then, in the page itself:
::CtaCustom{label="Community Forum" destination-key="communityForum" variant="primary" position="community"}
::
destination-key must match the name chosen in step 1 exactly (communityForum in both places above) - that's the link between the button and the destination it's registered to.
Requesting New Website Pages
If you would like the marketing team to create a new page, landing page, or apply website changes, use the Web Pages/Changes Request Form. See Requesting Work from Marketing for full details.
Starting with a draft copy is often the fastest way to get feedback and move into implementation.
In many cases, this helps keep changes focused and iterations small when the page is built.
For design-related considerations, see the Design Review process.
Where Files Go
The whole site is built with Nuxt. Markdown content lives in nuxt/content/ (blog posts in nuxt/content/blog/, changelog entries in nuxt/content/changelog/), and everything served as a file, images included, lives in nuxt/public/ at the path it is served from. The old src/ folder is gone: a file added there is never published, and the site's automated tests fail a pull request that adds one.
Pull Request Scope
To support fast iteration:
- prefer small, focused PRs
- avoid mixing content updates with layout or functional changes
- split larger changes into separate PRs where possible
This makes changes easier to review, test, and iterate on, and helps keep iterations small.
Reviewing Pull Requests
When creating a PR and requesting a review from a FlowFuse team member, please consider if the content is ready to be released ASAP or if its release should be to a particular schedule. If you don't want the reviewer to merge the content (in effect put it live on the website) mark the PR as a draft in GitHub. If you are reviewing a website PR and it is not marked as draft you can merge it, otherwise simply review and where appropriate approve the PR.