Kanorio

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/cloudflare or @astrojs/node, read on each request and cache by the X-Kanorio-Cache-Tags response header; a webhook handler built with createRevalidateHandler purges the tags in data.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.