v2.3 moves the pinned SDKs forward, adds a search composable, and drops the
argument useFlyoSitemap() never used. Every other composable and module option
keeps its signature.
| Dependency | v2.2 | v2.3 |
|---|---|---|
@flyo/nitro-vue3 |
^2.4.0 |
^2.5.0 |
@flyo/nitro-typescript |
(transitive ^1.4.0) |
direct ^1.7.0 |
@flyo/nitro-js-bridge |
transitive ^1.4.0 |
transitive ^1.5.0 |
@flyo/nitro-typescript is now a direct dependency because useFlyoSearch()
talks to the SDK's SearchApi. It is also pre-bundled by Vite alongside
@flyo/nitro-vue3 and the bridge, so it is not discovered lazily on the first
client import — a lazy discovery re-optimizes and hard-reloads the page, which
drops the live-edit connection inside the Flyo preview iframe.
The composable declared a required uniqueId parameter it never read. It is
gone, so the signature matches the one the README always documented:
-const { response } = await useFlyoSitemap('some-id')
+const { response } = await useFlyoSitemap()Sitemap items carry both values a sitemap entry needs. href is the finished
link — internal paths come with a trailing slash, mail links as mailto:… — and
updated_at is a Unix timestamp of the last content change, made for lastmod
(a rebuild that produces identical output does not move it). Items without an
href have no reachable URL and must be skipped.
const { response } = await useFlyoSitemap()
const urls = response.value
.filter(item => item.href)
.map(item => ({
loc: item.href,
lastmod: new Date(item.updated_at * 1000).toISOString()
}))routes is the raw map of route identifiers behind that href; reach for it
only when you need a specific named route.
The composable runs inside the Nuxt app, where the Flyo plugin has configured
the SDK — it is not available in a Nitro server/ route.
Full-text search across pages and entities, auto-imported like the other
composables. Results share the sitemap item shape, so a hit links through its
href and carries an updated_at timestamp:
<script setup lang="ts">
const query = ref('')
const { response: hits } = await useFlyoSearch(query, { sort: '-score' })
</script>
<template>
<input v-model="query">
<a v-for="hit in hits" :key="hit.entity_unique_id" :href="hit.href">
{{ hit.entity_title }}
</a>
</template>The query may be a plain string or a reactive source; a reactive query is
watched and re-runs the search on change, reporting a failure through the
returned error ref rather than throwing. options accepts sort (score,
title, time_start, updated_at, each reversible with a - prefix), page
and lang. An empty query resolves to an empty list without hitting the API, so
it is safe to bind straight to an input.
The API sends a routes map per sitemap item, carrying the resolved URL paths
plus a system key _empty. When _empty arrives as an explicit null, the SDK
used to drop the key; it now preserves it.
const { response } = await useFlyoSitemap()
// v2.2: { }
// v2.3: { _empty: null }
response.value[1].routes'_empty' in routes and Object.keys(routes).length now see the key:
// before
if ('_empty' in routes) { … }
// after — treats a missing key and an explicit null alike
if (routes._empty != null) { … }Reading a route path (routes.detail) is unchanged, and _empty: false /
_empty: true were always passed through untouched.
For TypeScript consumers of the SDK response types:
| Type | v2.2 | v2.3 |
|---|---|---|
FlyoSitemapResponse[number].routes |
Routes |
{ [key: string]: any } |
FlyoEntityResponse.entity.routes |
{ [key: string]: string } |
{ [key: string]: any } |
The old { [key: string]: string } was wrong — _empty is a boolean — so
routes._empty no longer needs a cast to be used as one. Reading a path still
type-checks as a string, but you lose string inference on the values, so add
a guard where the key is dynamic:
const path = routes[key]
if (typeof path !== 'string') return undefinedThe Routes type itself was removed from @flyo/nitro-typescript. This module
never re-exported it, so only a direct
import type { Routes } from '@flyo/nitro-typescript' in your own code stops
compiling — replace the annotation with { [key: string]: any }.
The SDK now models a missing meta image as false rather than an absent
string, so Meta.image is the union MetaImage = boolean | string. Guard it
before passing it to a meta tag:
const image = typeof page.meta_json.image === 'string' ? page.meta_json.image : undefinedWhen live-edit was enabled on a page that is not rendered inside the Flyo
preview iframe, every re-wire and every unmount posted an openEdit message to
the page's own window. The browser rejected it against the editor's target
origin and logged:
Failed to execute 'postMessage' on 'DOMWindow': The target origin provided ('https://flyo.cloud') does not match the recipient window's origin (…).
@flyo/nitro-vue3 now wires the hover affordance only when the page really is
embedded in the editor, so those errors are gone. Behaviour inside the editor is
unchanged, and no module option changed — liveEdit still defaults to on
outside production.
This section helps you upgrade from @flyo/nitro-nuxt v1 to v2.
- Module now requires Nuxt 4.3.1+ (upgraded from Nuxt 3)
- @nuxt/kit updated to v4.3.1
- All module composition APIs are Nuxt 4 compatible
Migration: Update your nuxt.config.ts to use Nuxt 4:
npm install nuxt@^4.3.1 @nuxt/kit@^4.3.1@flyo/nitro-vue3dependency upgraded from ^1.0.2 to ^2.0.0- Plugin initialization remains the same via
vueApp.use(FlyoVue, { ... }) - All composables maintain backward compatibility:
useFlyoConfig()useFlyoPage(slug)useFlyoEntity(uniqueId)useFlyoCurrentPage()useFlyoSitemap()
No action required for composable usage if using v1.
- Module builds now output
.mjsonly (no CommonJS.cjs) - TypeScript definitions are
.d.mtsformat - Build size optimized: 6.36 kB total
Migration: If you import from dist directly, ensure your bundler supports ES modules:
{
"type": "module",
"exports": {
".": {
"import": "./dist/module.mjs",
"types": "./dist/module.d.mts"
}
}
}dist/is now in.gitignore- Module is built during GitHub Actions release workflow
- CI automatically generates and publishes dist files
Impact: Releases are now reproducible and tamper-proof. No need to manage dist manually.
npm install @flyo/nitro-nuxt@^2.0.0Ensure your nuxt.config.ts uses valid module options:
export default defineNuxtConfig({
modules: [
['@flyo/nitro-nuxt', {
apiToken: process.env.FLYO_API_TOKEN,
apiBasePath: process.env.FLYO_API_BASE_PATH,
liveEdit: true,
liveEditOrigin: 'https://flyo.cloud',
registerPageRoutes: true,
defaultPageRoute: 'cms'
}]
]
})If registerPageRoutes: true (default), create the dynamic route handler:
pages/cms.vue:
<script setup lang="ts">
const { response: page } = await useFlyoCurrentPage()
</script>
<template>
<div>
<h1>{{ page?.title }}</h1>
<!-- Your CMS content here -->
</div>
</template>No changes from v1. Continue using:
FLYO_API_TOKEN=your-token
FLYO_API_BASE_PATH=https://api.flyo.test
FLYO_LIVE_EDIT=true
FLYO_LIVE_EDIT_ORIGIN=https://flyo.cloudVerify everything works:
npm run test # Run unit tests
npm run build # Build module
npm run playground:build # Verify playground integration- ✅ Plugin initialization unchanged
- ✅ Composable signatures unchanged
- ✅ Runtime config keys unchanged
- ✅ Environment variable names unchanged
- BREAKING: Module is now ESM-only (no CommonJS)
- BREAKING: Nuxt 3 is no longer supported (Nuxt 4+ required)
- BREAKING: dist folder no longer in git (built by CI)
If you're using this module as a dependency:
- Ensure your app uses Nuxt 4.3.1+
- Your bundler must support ES modules
- Composables work the same; no code changes needed
Ensure your .env file has:
FLYO_API_TOKEN=your-actual-tokenCreate the required page file:
touch pages/cms.vueRun the build first:
npm run buildFor issues with:
- This Nuxt module: Check GitHub repo
- @flyo/nitro-vue3: See @flyo/nitro-vue3 docs
- Nuxt 4: Visit Nuxt docs