Skip to main content

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

# .env
SKAYLE_API_BASE_URL=https://api.skayle.ai
SKAYLE_ORG_ID=your_organization_id
Update nuxt.config.ts:
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:
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:
<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:
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:
<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:
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.
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.