Skip to content

Nuxt Integration

Nuxt 3 and FlareBuilder are a natural fit. Use useFetch and useAsyncData for SSR-friendly data fetching, Nuxt’s server routes to add caching or header injection, or nuxt generate to pre-render a fully static site from your feed.

Prerequisites

  • A FlareBuilder organization with published content
  • A Nuxt 3 project
  • Your feed URL: https://your-org.flarebuilder.com/feed

Composable Helper

Create a shared composable so the feed URL and TypeScript types are defined once.

composables/useFeedApi.ts
export interface FeedItem {
id: string;
title: string;
type: string;
tags: string[];
date_published: string;
date_expires: string | null;
date_created: string;
author: { id: string; name?: string };
permalink: string;
sections: Array<{
id: string;
data: Record<string, unknown>;
}>;
}
export interface FeedResponse {
title: string;
description?: string;
feed_url: string;
items: FeedItem[];
pagination: {
next: string | null;
limit: number;
has_more: boolean;
};
}
const FEED_BASE = 'https://your-org.flarebuilder.com/feed';
/** Build a feed URL with optional filters */
function feedUrl(params: Record<string, string | number | undefined> = {}): string {
const qs = new URLSearchParams();
for (const [k, v] of Object.entries(params)) {
if (v !== undefined) qs.set(k, String(v));
}
return qs.size ? `${FEED_BASE}?${qs}` : FEED_BASE;
}
/** Fetch a page of feed items (SSR-safe via $fetch) */
export function useFeed(params: Record<string, string | number | undefined> = {}) {
return useFetch<FeedResponse>(feedUrl(params), {
key: `feed:${JSON.stringify(params)}`,
});
}
/** Fetch a single item by ID */
export function useFeedItem(id: string) {
return useFetch<FeedItem>(`https://your-org.flarebuilder.com/p/${id}`, {
key: `item:${id}`,
});
}

Display a Content List

Use useFeed directly in a page component. Nuxt runs useFetch on the server and hydrates the result on the client — no flash of empty content.

pages/news/index.vue
<script setup lang="ts">
import { useFeed } from '~/composables/useFeedApi';
const { data: feed, error } = await useFeed({ types: 'news', limit: 20 });
function section(item: any, id: string) {
return item.sections.find((s: any) => s.id === id)?.data ?? {};
}
</script>
<template>
<main>
<h1>{{ feed?.title }}</h1>
<p v-if="error">Failed to load content.</p>
<ul v-else>
<li v-for="item in feed?.items" :key="item.id">
<NuxtLink :to="`/news/${item.id}`">
<h2>{{ item.title }}</h2>
<p v-if="section(item, 'content').summary">
{{ section(item, 'content').summary }}
</p>
<time :datetime="item.date_published">
{{ new Date(item.date_published).toLocaleDateString() }}
</time>
</NuxtLink>
</li>
</ul>
</main>
</template>

Detail Page

pages/news/[id].vue
<script setup lang="ts">
import { useFeedItem } from '~/composables/useFeedApi';
const route = useRoute();
const { data: item, error } = await useFeedItem(route.params.id as string);
if (error.value) {
throw createError({ statusCode: 404, statusMessage: 'Not found' });
}
// Set page metadata from the item
useSeoMeta({
title: item.value?.title,
description: item.value?.sections[0]?.data?.summary as string | undefined,
});
const content = computed(() => item.value?.sections.find(s => s.id === 'content')?.data ?? {});
const schedule = computed(() => item.value?.sections.find(s => s.id === 'schedule')?.data ?? {});
</script>
<template>
<article v-if="item">
<h1>{{ item.title }}</h1>
<p v-if="schedule.event_start_date">
<time :datetime="(schedule.event_start_date as string)">
{{ new Date(schedule.event_start_date as string).toLocaleString() }}
</time>
</p>
<!-- Rich HTML from rich_text field -->
<div v-if="content.body" v-html="content.body" />
</article>
</template>

Pagination

Keep cursor in a reactive ref and replace the items list when the user loads more.

pages/news/index.vue
<script setup lang="ts">
import type { FeedItem, FeedResponse } from '~/composables/useFeedApi';
const FEED_BASE = 'https://your-org.flarebuilder.com/feed';
// Initial server-side fetch
const { data: initial } = await useFetch<FeedResponse>(
`${FEED_BASE}?templates=news&limit=10`,
{ key: 'news-page-1' }
);
const items = ref<FeedItem[]>(initial.value?.items ?? []);
const nextUrl = ref<string | null>(initial.value?.pagination.next ?? null);
const loading = ref(false);
async function loadMore() {
if (!nextUrl.value || loading.value) return;
loading.value = true;
try {
const data = await $fetch<FeedResponse>(nextUrl.value);
items.value.push(...data.items);
nextUrl.value = data.pagination.has_more ? data.pagination.next : null;
} finally {
loading.value = false;
}
}
</script>
<template>
<main>
<ul>
<li v-for="item in items" :key="item.id">
<NuxtLink :to="`/news/${item.id}`">{{ item.title }}</NuxtLink>
</li>
</ul>
<button v-if="nextUrl" :disabled="loading" @click="loadMore">
{{ loading ? 'Loading…' : 'Load more' }}
</button>
</main>
</template>

Filtering by Tag

Pass tags as a query param. You can make it reactive to drive a filter UI:

<script setup lang="ts">
import type { FeedResponse } from '~/composables/useFeedApi';
const FEED_BASE = 'https://your-org.flarebuilder.com/feed';
const activeTag = ref<string | null>(null);
const feedUrl = computed(() => {
const qs = new URLSearchParams({ types: 'news', limit: '20' });
if (activeTag.value) qs.set('tags', activeTag.value);
return `${FEED_BASE}?${qs}`;
});
const { data: feed, refresh } = await useFetch<FeedResponse>(feedUrl);
// Re-fetch whenever the tag changes
watch(activeTag, () => refresh());
const allTags = ['featured', 'announcement', 'update'];
</script>
<template>
<div>
<nav>
<button @click="activeTag = null" :class="{ active: !activeTag }">All</button>
<button
v-for="tag in allTags"
:key="tag"
@click="activeTag = tag"
:class="{ active: activeTag === tag }"
>
{{ tag }}
</button>
</nav>
<ul>
<li v-for="item in feed?.items" :key="item.id">{{ item.title }}</li>
</ul>
</div>
</template>

Static Site Generation

For a fully static build, Nuxt can pre-render all feed pages at build time.

  1. Set nuxt.config.ts to pre-render the feed routes:

    nuxt.config.ts
    export default defineNuxtConfig({
    nitro: {
    prerender: {
    crawlLinks: true,
    routes: ['/news'], // starting points; Nuxt will crawl NuxtLink hrefs
    },
    },
    });
  2. Add a routes hook to enumerate all item pages:

    nuxt.config.ts
    import type { FeedItem } from './composables/useFeedApi';
    export default defineNuxtConfig({
    hooks: {
    async 'nitro:config'(nitroConfig) {
    // Fetch all items and register their routes for prerendering
    const FEED_BASE = 'https://your-org.flarebuilder.com/feed';
    const routes: string[] = [];
    let url: string | null = `${FEED_BASE}?templates=news&limit=100`;
    while (url) {
    const data = await $fetch<{ items: FeedItem[]; pagination: { has_more: boolean; next: string | null } }>(url);
    routes.push(...data.items.map(item => `/news/${item.id}`));
    url = data.pagination.has_more ? data.pagination.next : null;
    }
    nitroConfig.prerender ??= {};
    nitroConfig.prerender.routes ??= [];
    (nitroConfig.prerender.routes as string[]).push(...routes);
    },
    },
    });
  3. Run nuxt generate to build the static site.


Server Route (optional)

If you want to add server-side logic — extra caching headers, request logging, or secret token injection for private channel feeds — add a Nuxt server route that proxies the Feed API.

server/api/feed.get.ts
import type { FeedResponse } from '~/composables/useFeedApi';
export default defineEventHandler(async (event) => {
const query = getQuery(event);
const qs = new URLSearchParams();
for (const [k, v] of Object.entries(query)) {
if (v) qs.set(k, String(v));
}
const data = await $fetch<FeedResponse>(
`https://your-org.flarebuilder.com/feed?${qs}`
);
// Set a cache header on the server route response
setResponseHeader(event, 'Cache-Control', 'public, s-maxage=60');
return data;
});

Then call /api/feed?templates=news from your pages instead of the FlareBuilder URL directly.


<template>
<a href="https://your-org.flarebuilder.com/feed?format=ics&templates=event" download>
Subscribe to calendar (.ics)
</a>
</template>