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.
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.
<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
<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 itemuseSeoMeta({ 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.
<script setup lang="ts">import type { FeedItem, FeedResponse } from '~/composables/useFeedApi';
const FEED_BASE = 'https://your-org.flarebuilder.com/feed';
// Initial server-side fetchconst { 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 changeswatch(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.
-
Set
nuxt.config.tsto pre-render the feed routes:nuxt.config.ts export default defineNuxtConfig({nitro: {prerender: {crawlLinks: true,routes: ['/news'], // starting points; Nuxt will crawl NuxtLink hrefs},},}); -
Add a
routeshook 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 prerenderingconst 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);},},}); -
Run
nuxt generateto 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.
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.
ICS Calendar Link
<template> <a href="https://your-org.flarebuilder.com/feed?format=ics&templates=event" download> Subscribe to calendar (.ics) </a></template>