Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 0 additions & 3 deletions nuxt/app.vue
Original file line number Diff line number Diff line change
@@ -1,7 +1,4 @@
<script setup lang="ts">
// Disable nuxt-seo-utils' automatic "| SiteName" suffix — our pages already carry the brand
useHead({ titleTemplate: '%s' })

useSchemaOrg([
defineOrganization({
name: 'FlowFuse',
Expand Down
6 changes: 0 additions & 6 deletions nuxt/components/BlogListing.vue
Original file line number Diff line number Diff line change
Expand Up @@ -14,12 +14,6 @@ const featuredAuthorNames = computed(() => useAuthorNames(featured.value?.author
const featuredSummary = computed(() => featured.value?.description || featured.value?.meta?.description || '')
const featuredImage = computed(() => featured.value?.image || '/images/og-blog.jpg')
const featuredDate = computed(() => featured.value ? new Date(featured.value.date).toLocaleDateString('en-US', { year: 'numeric', month: 'short', day: 'numeric' }) : '')

const pageTitle = computed(() => props.tag ? 'Blog' : 'Blog')
useSeoMeta({
title: pageTitle,
ogTitle: pageTitle,
})
</script>

<template>
Expand Down
1 change: 0 additions & 1 deletion nuxt/components/ChangelogListing.vue
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@ const { entries, totalPages } = useChangelogList(props.page)

useSeoMeta({
title: 'Changelog',
ogTitle: 'Changelog',
})
</script>

Expand Down
4 changes: 4 additions & 0 deletions nuxt/nuxt.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -261,6 +261,10 @@ export default defineNuxtConfig({

app: {
head: {
// nuxt-seo-utils' default title template is `%s %separator %siteName`; the
// separator defaults to '|' if unset. This makes every page's <title>/og:title
// "{page title} • FlowFuse" without each page having to append the brand itself.
templateParams: { separator: '•' },
link: [
{ rel: 'stylesheet', href: '/css/style.css' },
{ rel: 'preconnect', href: 'https://fonts.googleapis.com' },
Expand Down
2 changes: 1 addition & 1 deletion nuxt/pages/[...slug].vue
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ if (!page.value) {
// computes its own X-Robots-Tag/meta before this component ever runs, so a manual
// `useHead` robots meta tag here would only race against it, not control it.
useHead({
title: `${page.value.title} • FlowFuse`
title: page.value.title
})
</script>

Expand Down
12 changes: 9 additions & 3 deletions nuxt/pages/application-guide/[guide]/[slug].vue
Original file line number Diff line number Diff line change
Expand Up @@ -33,13 +33,19 @@ const next = computed(() =>
: null
)

const fullTitle = computed(() => `${page.value?.title} • ${guide.value?.title} • FlowFuse Application Guide`)
const baseTitle = computed(() => `${page.value?.title} • ${guide.value?.title}`)
const description = computed(() => page.value?.blurb || guide.value?.tagline || '')

// Guide pages always get the "Application Guide" qualifier on the brand name.
// og:title infers from the resolved title. Needs an explicit high tagPriority in
// its own useHead call — nuxt-seo-utils pushes its own global siteName with
// tagPriority: 'low', and that otherwise wins over a page-level override
// regardless of registration order.
useHead({ templateParams: { siteName: 'FlowFuse Application Guide' } }, { tagPriority: 1000 })

useSeoMeta({
title: fullTitle,
title: baseTitle,
description,
ogTitle: fullTitle,
ogDescription: description,
ogUrl: computed(() => `https://flowfuse.com${route.path}`),
ogType: 'article',
Expand Down
3 changes: 1 addition & 2 deletions nuxt/pages/application-guide/index.vue
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,12 @@ const guides = computed(() => GUIDES.map(guide => ({
pages: pagesForGuide(pages.value, guide.id),
})))

const title = 'FlowFuse Application Guide'
const title = 'Application Guide'
const description = 'How to build and deploy with FlowFuse and Node-RED: the packages, the architectures and the flow patterns we teach during a proof of concept.'

useSeoMeta({
title,
description,
ogTitle: title,
ogDescription: description,
ogUrl: 'https://flowfuse.com/application-guide/',
ogType: 'website',
Expand Down
13 changes: 10 additions & 3 deletions nuxt/pages/blog/[...slug].vue
Original file line number Diff line number Diff line change
Expand Up @@ -80,14 +80,21 @@ const tldrText = computed(() => typeof page.value?.tldr === 'string' ? page.valu
const pageTitle = computed(() => page.value?.title || 'Blog')
provide('blogPostTitle', pageTitle)
const pageDescription = computed(() => page.value?.description || page.value?.meta?.description || '')
const fullTitle = computed(() => `${pageTitle.value} • FlowFuse Blog`)
const canonicalUrl = computed(() => `https://flowfuse.com${route.path}`)
const absoluteImage = computed(() => heroImage.value.startsWith('http') ? heroImage.value : `https://flowfuse.com${heroImage.value}`)

// Posts get the "Blog" qualifier on the brand name; the listing doesn't (its own
// title already says "Blog"). og:title infers from the resolved title. Needs an
// explicit high tagPriority in its own useHead call — nuxt-seo-utils pushes its own
// global siteName with tagPriority: 'low', and that otherwise wins over a
// page-level override regardless of registration order.
useHead({
templateParams: { siteName: () => routeInfo.value.kind === 'post' ? 'FlowFuse Blog' : 'FlowFuse' },
}, { tagPriority: 1000 })

useSeoMeta({
title: computed(() => routeInfo.value.kind === 'post' ? fullTitle.value : 'Blog'),
title: pageTitle,
description: computed(() => routeInfo.value.kind === 'post' ? pageDescription.value : ''),
ogTitle: computed(() => routeInfo.value.kind === 'post' ? fullTitle.value : 'Blog'),
ogDescription: computed(() => routeInfo.value.kind === 'post' ? pageDescription.value : ''),
ogImage: computed(() => routeInfo.value.kind === 'post' ? absoluteImage.value : undefined),
ogUrl: canonicalUrl,
Expand Down
9 changes: 7 additions & 2 deletions nuxt/pages/blog/author/[slug].vue
Original file line number Diff line number Diff line change
Expand Up @@ -23,17 +23,22 @@ const { data: posts } = await useAsyncData(

const topics = computed(() => author.value?.knowsAbout || [])

const pageTitle = computed(() => `${author.value?.name} - FlowFuse Blog`)
const pageTitle = computed(() => author.value?.name ?? '')
const pageDescription = computed(() => {
const member = author.value
if (!member) return ''
return `Articles by ${member.name}${member.title ? `, ${member.title}` : ''} on the FlowFuse blog.`
})

// Author pages always get the "Blog" qualifier on the brand name. og:title infers
// from the resolved title. Needs an explicit high tagPriority in its own useHead
// call — nuxt-seo-utils pushes its own global siteName with tagPriority: 'low',
// and that otherwise wins over a page-level override regardless of registration order.
useHead({ templateParams: { siteName: 'FlowFuse Blog' } }, { tagPriority: 1000 })

useSeoMeta({
title: pageTitle,
description: pageDescription,
ogTitle: pageTitle,
ogDescription: pageDescription,
ogUrl: computed(() => `https://flowfuse.com${route.path}`),
ogType: 'profile',
Expand Down
11 changes: 8 additions & 3 deletions nuxt/pages/changelog/[...slug].vue
Original file line number Diff line number Diff line change
Expand Up @@ -36,13 +36,18 @@ function issueLabel(issue: string): string {
}

const pageTitle = computed(() => page.value?.title || 'Changelog')
const fullTitle = computed(() => `${pageTitle.value} • FlowFuse Changelog`)
const canonicalUrl = computed(() => `https://flowfuse.com${route.path}`)

// Changelog entries always get the "Changelog" qualifier on the brand name.
// og:title infers from the resolved title. Needs an explicit high tagPriority in
// its own useHead call — nuxt-seo-utils pushes its own global siteName with
// tagPriority: 'low', and that otherwise wins over a page-level override
// regardless of registration order.
useHead({ templateParams: { siteName: 'FlowFuse Changelog' } }, { tagPriority: 1000 })

useSeoMeta({
title: fullTitle,
title: pageTitle,
description: computed(() => page.value?.description || ''),
ogTitle: fullTitle,
ogDescription: computed(() => page.value?.description || ''),
ogUrl: canonicalUrl,
ogType: 'article',
Expand Down
5 changes: 4 additions & 1 deletion nuxt/pages/docs/[...slug].vue
Original file line number Diff line number Diff line change
Expand Up @@ -30,11 +30,14 @@ if (page.value.layout === 'redirect' && page.value.redirect?.to) {
const pageTitle = computed(() => page.value?.navTitle || page.value?.title || slugParts.value.at(-1) || 'Documentation')

useHead({
title: computed(() => slugParts.value.length ? `${pageTitle.value} • FlowFuse Docs` : 'FlowFuse Documentation'),
title: pageTitle,
meta: [
{ name: 'description', content: computed(() => (page.value as any)?.meta?.description || '') },
],
})
useHead({
templateParams: { siteName: () => slugParts.value.length ? 'FlowFuse Docs' : 'FlowFuse' },
}, { tagPriority: 1000 })

// Same key+handler DocsLeftNav uses, so useAsyncData dedupes into one fetch per request.
const { data: navGroups } = await useDocsNavTree()
Expand Down
2 changes: 1 addition & 1 deletion nuxt/pages/ebooks/[slug].vue
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ if (!page.value) {
const coverImage = computed(() => page.value?.coverImage ?? page.value?.image)

useHead({
title: `${page.value.meta.title} • FlowFuse`,
title: page.value.meta.title,
meta: [
{ name: 'description', content: page.value.meta?.description ?? '' },
]
Expand Down
13 changes: 10 additions & 3 deletions nuxt/pages/handbook/[...slug].vue
Original file line number Diff line number Diff line change
Expand Up @@ -21,13 +21,20 @@ if (!page.value) {
}

const pageTitle = computed(() => page.value?.title || slugParts.value.at(-1) || 'Handbook')
const fullTitle = computed(() => slugParts.value.length ? `${pageTitle.value} • FlowFuse Handbook` : 'FlowFuse Handbook')
const canonicalUrl = computed(() => `https://flowfuse.com${route.path}`)

// Nested pages get the "Handbook" qualifier on the brand name; the section root
// doesn't (its own title already says "Handbook"). og:title infers from the
// resolved title. Needs an explicit high tagPriority in its own useHead call —
// nuxt-seo-utils pushes its own global siteName with tagPriority: 'low', and that
// otherwise wins over a page-level override regardless of registration order.
useHead({
templateParams: { siteName: () => slugParts.value.length ? 'FlowFuse Handbook' : 'FlowFuse' },
}, { tagPriority: 1000 })

useSeoMeta({
title: fullTitle,
title: pageTitle,
description: computed(() => page.value?.description || ''),
ogTitle: fullTitle,
ogDescription: computed(() => page.value?.description || ''),
ogUrl: canonicalUrl,
ogType: 'article',
Expand Down
8 changes: 7 additions & 1 deletion nuxt/pages/integrations/[...id].vue
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,13 @@ const authorIsLinkable = computed(() => {
return Boolean(url) && (url.includes('github.com') || url.includes('npmjs.com'))
})

useHead({ title: () => `${node.value._id} • FlowFuse Integrations` })
// Integration pages always get the "Integrations" qualifier on the brand name.
// og:title infers from the resolved title. Needs an explicit high tagPriority in
// its own useHead call — nuxt-seo-utils pushes its own global siteName with
// tagPriority: 'low', and that otherwise wins over a page-level override
// regardless of registration order.
useHead({ templateParams: { siteName: 'FlowFuse Integrations' } }, { tagPriority: 1000 })
useHead({ title: () => node.value._id })

const activeTab = ref<'overview' | 'examples'>('overview')
function switchTab (tab: 'overview' | 'examples') {
Expand Down
2 changes: 1 addition & 1 deletion nuxt/pages/integrations/index.vue
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import { nodeProducts } from '../../utils/integrations-ui'
type CatalogueNode = IntegrationCatalogEntry & { _idLc: string }
type ProductFilter = 'all' | CertifiedCollection

useHead({ title: 'Integrations • FlowFuse' })
useHead({ title: 'Integrations' })

const route = useRoute()
const router = useRouter()
Expand Down
2 changes: 1 addition & 1 deletion nuxt/pages/pricing/index.vue
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ function featureDescription (feature: CatalogFeature) {
}

useSeoMeta({
title: 'FlowFuse Pricing',
title: 'Pricing',
description: 'Products built around the job, deploying and scaling automation across plants, or integrating the business systems and data sources you run on. All governed on one platform.',
ogUrl: 'https://flowfuse.com/pricing/',
twitterSite: '@FlowFuseinc',
Expand Down
2 changes: 1 addition & 1 deletion nuxt/pages/product/index.vue
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
// unified destinations, so it keeps a plain ff-btn link, same precedent as
// BlogPostCta.vue's pricing fallback.
useSeoMeta({
title: 'FlowFuse Features',
title: 'Features',
description: 'FlowFuse provides the features companies require to reliably deliver industrial applications to devices and cloud in a collaborative, secure manner.',
ogUrl: 'https://flowfuse.com/product/',
twitterSite: '@FlowFuseinc',
Expand Down
2 changes: 1 addition & 1 deletion nuxt/pages/resources/publications.vue
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ const publications = [
]

useHead({
title: 'Publications: eBooks and Whitepapers • FlowFuse',
title: 'Publications: eBooks and Whitepapers',
meta: [
{ name: 'description', content: 'Browse our library of whitepapers and eBooks and discover the power of our low-code industrial data platform.' },
]
Expand Down
2 changes: 1 addition & 1 deletion nuxt/pages/whitepaper/[slug].vue
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ if (!page.value) {
}

useHead({
title: `${page.value.meta?.title} • FlowFuse`,
title: page.value.meta?.title,
meta: [
{ name: 'description', content: page.value.meta?.description ?? '' },
]
Expand Down