@lvlz/sdk
v0.1.2
Published
Fetch and render your LVLZ.ai published posts on any site (Next.js, React, Vite, plain HTML).
Readme
@lvlz/sdk
Fetch and render your LVLZ.ai published posts on any site — Next.js, React, Vite, or plain HTML. LVLZ generates SEO/GEO-optimized blog posts; this SDK puts them on your domain so they boost your search and AI-answer visibility.
For SEO/GEO, render on the server. AI crawlers (GPTBot, ClaudeBot, PerplexityBot) usually don't run JavaScript, and Google's JS rendering is unreliable. The Next.js recipe below server-renders your posts — that's what actually moves the needle. The
<script>embed is a convenience for sites without a build step.
Install
npm install @lvlz/sdkGet a publishable key (lvlz_pub_…) from LVLZ → Settings → Integrations → Website SDK.
It's read-only and brand-scoped, so it's safe to ship in client code.
Core client
import { createClient } from '@lvlz/sdk';
const lvlz = createClient({ apiKey: 'lvlz_pub_…' });
const { posts } = await lvlz.getPosts({ limit: 20 });
const post = await lvlz.getPost('my-post-slug'); // null if not foundNext.js (recommended — SSR + ISR + metadata)
app/blog/page.tsx
import { createClient } from '@lvlz/sdk';
import { LvlzPostList } from '@lvlz/sdk/react';
const lvlz = createClient({ apiKey: process.env.LVLZ_KEY! });
export const revalidate = 600; // re-check for new posts every 10 minutes
export default async function Blog() {
const { posts } = await lvlz.getPosts();
return <LvlzPostList posts={posts} />;
}app/blog/[slug]/page.tsx
import { notFound } from 'next/navigation';
import type { Metadata } from 'next';
import { createClient } from '@lvlz/sdk';
import { LvlzArticle } from '@lvlz/sdk/react';
const lvlz = createClient({ apiKey: process.env.LVLZ_KEY! });
export const revalidate = 600;
export async function generateStaticParams() {
const { posts } = await lvlz.getPosts({ limit: 100 });
return posts.map((p) => ({ slug: p.slug }));
}
export async function generateMetadata(
{ params }: { params: Promise<{ slug: string }> },
): Promise<Metadata> {
const { slug } = await params;
const post = await lvlz.getPost(slug);
if (!post) return {};
return {
title: post.title,
description: post.description ?? post.excerpt ?? undefined,
keywords: post.keywords,
openGraph: { title: post.title, description: post.excerpt ?? undefined, type: 'article' },
};
}
export default async function PostPage({ params }: { params: Promise<{ slug: string }> }) {
const { slug } = await params;
const post = await lvlz.getPost(slug);
if (!post) notFound();
return <LvlzArticle post={post} className="prose mx-auto" />;
}LvlzArticle also injects a BlogPosting JSON-LD <script> for richer SEO/GEO.
React / Vite (client-side)
import { useEffect, useState } from 'react';
import { createClient, type PublicPost } from '@lvlz/sdk';
import { LvlzPostList } from '@lvlz/sdk/react';
const lvlz = createClient({ apiKey: import.meta.env.VITE_LVLZ_KEY });
export function Blog() {
const [posts, setPosts] = useState<PublicPost[]>([]);
useEffect(() => {
lvlz.getPosts().then((r) => setPosts(r.posts));
}, []);
return <LvlzPostList posts={posts} />;
}Plain HTML (no build step)
<div id="lvlz-blog"></div>
<script
src="https://app.lvlz.ai/sdk.js"
data-key="lvlz_pub_…"
data-target="#lvlz-blog"
></script>The embed lists your posts and renders a single post when the URL has ?lvlz_slug=….
Add data-slug="my-post-slug" to render one specific post.
API
| Method | Returns |
| --- | --- |
| createClient({ apiKey, baseUrl?, fetchOptions? }) | LvlzClient |
| client.getPosts({ limit?, offset? }) | { posts, total, limit, offset } |
| client.getPost(slug) | PublicPost \| null |
Caching
The feed is edge-cached (s-maxage=300, stale-while-revalidate=86400), so reads are fast and
won't hammer your quota. Two things to know:
- Empty responses are never cached — as soon as your first post is published it shows up (no stale empty feed).
getPosts()always sendslimit/offset, so every call hits one canonical, cacheable URL.
Control the framework-level cache via fetchOptions:
// Next.js — ISR: re-fetch at most every 10 minutes (pairs with `export const revalidate`)
const lvlz = createClient({
apiKey: process.env.LVLZ_KEY!,
fetchOptions: { next: { revalidate: 600 } },
});
// Always fresh (e.g. a preview/dev environment)
const lvlz = createClient({ apiKey: '…', fetchOptions: { cache: 'no-store' } });New posts appear within the s-maxage window (≤5 min) of your revalidate interval.
