> ## Documentation Index
> Fetch the complete documentation index at: https://docs.skayle.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# Nuxt

> Integrate Skayle CMS with Nuxt 4 using useFetch, dynamic routes, and static-friendly rendering.

## Overview

This guide uses:

* Nuxt 4.x
* `useFetch` with server rendering defaults
* Blog listing + dynamic detail pages
* Static/prerender-friendly setup

## 1. Configure environment variables

```bash theme={null}
# .env
SKAYLE_API_BASE_URL=https://api.skayle.ai
SKAYLE_ORG_ID=your_organization_id
```

Update `nuxt.config.ts`:

```ts theme={null}
export default defineNuxtConfig({
	runtimeConfig: {
		skayleApiBaseUrl: process.env.SKAYLE_API_BASE_URL || "https://api.skayle.ai",
		skayleOrgId: process.env.SKAYLE_ORG_ID,
	},
	routeRules: {
		"/blog": { swr: 300 },
		"/blog/**": { swr: 300 },
	},
});
```

## 2. Add a small CMS client

Create `server/utils/skayle-cms.ts`:

```ts theme={null}
export type JsonApiResource<T = Record<string, unknown>> = {
	id: string;
	type: string;
	attributes: T;
};

export async function fetchSkayle<T>(path: string) {
	const config = useRuntimeConfig();
	const orgId = config.skayleOrgId;
	if (!orgId) throw new Error("Missing SKAYLE_ORG_ID");

	return $fetch<T>(`${config.skayleApiBaseUrl}/v1/${orgId}${path}`);
}
```

## 3. Create blog listing page

Create `pages/blog/index.vue`:

```vue theme={null}
<script setup lang="ts">
import type { JsonApiResource } from "~/server/utils/skayle-cms";

type CmsPost = { title: string; slug: string; excerpt: string | null };

type ListResponse = {
	data: Array<JsonApiResource<CmsPost>>;
};

const { data, error } = await useFetch<ListResponse>("/api/blog-posts");

if (error.value) {
	throw createError({ statusCode: 500, statusMessage: "Failed to load blog posts" });
}
</script>

<template>
	<main>
		<h1>Blog</h1>
		<ul>
			<li v-for="post in data?.data || []" :key="post.id">
				<NuxtLink :to="`/blog/${post.attributes.slug}`">
					{{ post.attributes.title }}
				</NuxtLink>
			</li>
		</ul>
	</main>
</template>
```

Create `server/api/blog-posts.get.ts`:

```ts theme={null}
import { fetchSkayle } from "~/server/utils/skayle-cms";

export default defineEventHandler(async () => {
	return fetchSkayle("/articles?status=published&orderby=date&order=desc&page=1&per_page=50");
});
```

## 4. Create dynamic blog detail page

Create `pages/blog/[slug].vue`:

```vue theme={null}
<script setup lang="ts">
const route = useRoute();

const { data, error } = await useFetch(`/api/blog-posts/${route.params.slug}`);

if (error.value) {
	throw createError({ statusCode: 404, statusMessage: "Post not found" });
}
</script>

<template>
	<article v-if="data?.data">
		<h1>{{ data.data.attributes.title }}</h1>
		<p v-if="data.data.attributes.excerpt">{{ data.data.attributes.excerpt }}</p>
		<div v-html="data.data.attributes.content_html || ''" />
	</article>
</template>
```

Create `server/api/blog-posts/[slug].get.ts`:

```ts theme={null}
import { fetchSkayle } from "~/server/utils/skayle-cms";

export default defineEventHandler(async (event) => {
	const slug = getRouterParam(event, "slug");
	return fetchSkayle(`/articles/${encodeURIComponent(slug || "")}?include=categories,tags,authors`);
});
```

## 5. Static generation guidance

For static output:

* Use `nuxi generate`.
* Keep `/blog` and `/blog/**` route rules with SWR/ISR behavior where needed.
* If you need fully pre-rendered dynamic paths, pre-seed route discovery from `/articles` during build.

## 6. Use non-article collections (optional)

For additional content types/collections, call collection-scoped items.
Non-article collections are available on the `Authority` plan.

```ts theme={null}
export default defineEventHandler(async () => {
	return fetchSkayle("/collections/answers/items?status=published&page=1&per_page=50");
});
```

`/posts` remains available for backward compatibility, but `/articles` is the primary endpoint.
