Get started
Astro
Build a fast Astro site on Kanorio content, with static or server rendering, rebuilds on publish, SEO files and edge redirects.
Astro ships no JavaScript by default, which makes it a good fit for brand sites and blogs. Read Kanorio content with fetch in the component frontmatter, at build time for static sites or per request with a server adapter.
1. Environment variable
# .env PUBLIC_KANORIO_SITE_URL="https://s-xxxxxxxxxxxx.kanorio.com"
Use the permanent system URL from Site settings › Developer.
2. List a collection
--- // src/pages/projects.astro const SITE_URL = import.meta.env.PUBLIC_KANORIO_SITE_URL; const res = await fetch(`${SITE_URL}/api/v1/collections/projects/items?sort=-publishedAt&limit=20`); const { data, assets } = await res.json(); --- <section> <h1>Projects</h1> {data.map((item) => { const cover = assets[item.fieldData.cover]; return ( <article> {cover && <img src={cover.url} alt={cover.alt ?? ""} width={cover.width} height={cover.height} loading="lazy" />} <h2>{item.fieldData.title}</h2> <p>{item.fieldData.summary}</p> </article> ); })} </section>
3. Blog posts
For a static build, getStaticPaths walks every page of the blog with nextCursor:
--- // src/pages/blog/[slug].astro const SITE_URL = import.meta.env.PUBLIC_KANORIO_SITE_URL; export async function getStaticPaths() { const siteUrl = import.meta.env.PUBLIC_KANORIO_SITE_URL; const slugs = []; let cursor = null; do { const url = new URL(`${siteUrl}/api/v1/collections/blog/items`); url.searchParams.set("fields", "title"); url.searchParams.set("limit", "100"); if (cursor) url.searchParams.set("cursor", cursor); const page = await (await fetch(url)).json(); slugs.push(...page.data.map((post) => post.slug)); cursor = page.hasMore ? page.nextCursor : null; } while (cursor); return slugs.map((slug) => ({ params: { slug } })); } const { slug } = Astro.params; const res = await fetch(`${SITE_URL}/api/v1/collections/blog/items/${encodeURIComponent(slug)}?expand=authors,categories&formats=html`); if (!res.ok) return Astro.redirect("/404"); const { item, assets } = await res.json(); const cover = item.fieldData.cover_image ? assets[item.fieldData.cover_image] : null; --- <article> <h1>{item.fieldData.title}</h1> {cover && <img src={cover.url} alt={cover.alt ?? ""} />} {item.toc?.length > 0 && ( <nav aria-label="Contents"> <ul>{item.toc.map((h) => <li data-level={h.level}><a href={`#${h.id}`}>{h.label}</a></li>)}</ul> </nav> )} <div class="prose" set:html={item.contentHtml} /> </article>
4. Forms and updates
- Forms. Use
<kanorio-form>from Elements, or the challenge and submit endpoints described in Forms. Add your domain to the allowed origins. - Static builds. Create a deploy hook on your host and add it as a webhook under Site settings › Webhooks, subscribed to the collection and page events. Every publish triggers a rebuild.
- Server rendering. With
@astrojs/cloudflareor@astrojs/node, read on each request and cache by theX-Kanorio-Cache-Tagsresponse header; a webhook handler built withcreateRevalidateHandlerpurges the tags indata.cacheTags(the same vocabulary, see Webhooks).
5. SEO files
Astro has no proxy rewrites, and redirects would send crawlers to another domain. Serve the files from endpoints instead. seoRewrites returns the source URL for each path:
// src/lib/kanorio-seo.ts import { seoRewrites } from "@kanorio/sdk"; const targets = seoRewrites({ siteUrl: import.meta.env.PUBLIC_KANORIO_SITE_URL, framework: "astro" }); export async function proxySeoFile(path: string): Promise<Response> { const res = await fetch(targets[path]); return new Response(res.body, { status: res.status, headers: { "Content-Type": res.headers.get("Content-Type") ?? "text/plain; charset=utf-8" }, }); }
// src/pages/robots.txt.ts, and the same for sitemap.xml, llms.txt, llms-full.txt and feed.xml import { proxySeoFile } from "../lib/kanorio-seo"; export const GET = () => proxySeoFile("/robots.txt");
Add JSON-LD to each page with fetchDeliveryJsonLd:
--- import { fetchDeliveryJsonLd } from "@kanorio/sdk"; const jsonLd = await fetchDeliveryJsonLd({ siteUrl: import.meta.env.PUBLIC_KANORIO_SITE_URL, path: Astro.url.pathname }); --- {jsonLd && <script type="application/ld+json" set:html={jsonLd.scriptJson} />}
6. Redirects and crawler rules
With a server adapter, apply the dashboard's redirects and AI crawler rules, and serve the IndexNow key file, in middleware:
// src/middleware.ts import { defineMiddleware } from "astro:middleware"; import { createKanorioMiddleware } from "@kanorio/sdk/edge"; const kanorioEdge = createKanorioMiddleware({ siteUrl: import.meta.env.PUBLIC_KANORIO_SITE_URL }); export const onRequest = defineMiddleware(async (context, next) => { const ctx = context.locals.runtime?.ctx; // Cloudflare adapter only const response = await kanorioEdge.handleRequest(context.request, { waitUntil: ctx ? (promise) => ctx.waitUntil(promise) : undefined, }); return response ?? next(); });
Static builds cannot run middleware. Configure redirects on your host, and forward /{key}.txt to https://{site}/api/v1/seo/indexnow-key.txt. The middleware matches whole paths only; add wildcard redirects to redirects in astro.config.
7. Analytics
Load the Elements script once in your layout and add the analytics element:
<script type="module" src="https://cdn.kanorio.com/elements/v1/kanorio-elements.js"></script> <kanorio-analytics site-url="https://s-xxxxxxxxxxxx.kanorio.com"></kanorio-analytics>
Pageviews, referrers, campaign parameters and visits from AI assistants then appear in the dashboard's analytics.