npm.io
0.2.2 • Published 10h ago

@rankveo/astro

Licence
MIT
Version
0.2.2
Deps
0
Vulns
0
Weekly
0

@rankveo/astro

Run your rankveo blog on an Astro site. rankveo is the CMS: you approve and publish there, your site reads the article and renders it under your own domain, design, and URLs.

npm install @rankveo/astro

Content collection

The integration is a Content Layer loader, so articles behave like any other collection — typed, cached between builds, and rendered with <Content />.

// src/content.config.ts
import { defineCollection } from 'astro:content';
import { rankveoLoader } from '@rankveo/astro';

const blog = defineCollection({
  loader: rankveoLoader({ apiKey: import.meta.env.RANKVEO_BLOG_API_KEY }),
});

export const collections = { blog };

Pass the key explicitly. Astro loads .env into import.meta.env, not process.env, so the client's own environment fallback cannot see it here. A variable without a PUBLIC_ prefix never reaches the browser.

RANKVEO_BLOG_API_KEY=rk_your_key_here

Then render:

---
import { getCollection, render } from 'astro:content';
export async function getStaticPaths() {
  const articles = await getCollection('blog');
  return articles.map((article) => ({ params: { slug: article.id }, props: { article } }));
}
const { article } = Astro.props;
const { Content } = await render(article);
---
<article class="article-body"><Content /></article>

The loader stores the body under rendered, which is what makes render() and <Content /> work, and keeps a large HTML blob out of the validated data.

Starter

cp -r node_modules/@rankveo/astro/starter/src/* src/
Route File
/blog, /blog/2 pages/blog/[...page].astro
/blog/[slug] pages/blog/[slug].astro
/blog/tag/[slug] pages/blog/tag/[slug]/[...page].astro
/blog/sitemap.xml pages/blog/sitemap.xml.ts
/blog/rss.xml pages/blog/rss.xml.ts

Set your site URL and page size in src/rankveo.ts.

Pagination uses Astro's paginate(), so pages are real paths — static output has no request-time search params, and there is no zero-based/one-based conversion to get wrong.

Images

Ask the API which hosts your articles use, rather than guessing:

import { remotePatterns } from '@rankveo/astro';

const { imageHosts } = await blog.getSite();
console.log(JSON.stringify(remotePatterns(imageHosts), null, 2));

Paste into astro.config.mjs. Note the key nests under image, singular — unlike Next.js:

image: { remotePatterns: [{ protocol: 'https', hostname: 'cdn.example.com' }] }

Article styling

starter/src/styles/article.css styles the article HTML under .article-body. It has no dependencies and is meant to be edited.

Do not drop it and rely on your own reset. Most resets — Tailwind's preflight included — ship ol, ul, menu { list-style: none } and h1…h6 { font-size: inherit; font-weight: inherit }. With those in force and nothing restoring them, an article renders as one flat wall of text: headings the same size as body copy, lists with no markers.

Keeping the blog fresh

Static output has no runtime cache to invalidate — the site must be rebuilt. Point rankveo at your host's deploy hook (Cloudflare Pages, Netlify, Vercel) and publishing triggers a build.

Server output can invalidate a single page. Copy the revalidate endpoint:

cp -r node_modules/@rankveo/astro/starter/server/pages/api src/pages/api

It is kept out of the default starter deliberately: it carries export const prerender = false, which requires an adapter, so copying it into a static project fails the build with NoAdapterInstalled.

Set RANKVEO_REVALIDATE_SECRET, then paste the URL and secret into rankveo → Integrations. Wire cache.invalidate() inside the handler to whatever your adapter uses — the endpoint verifies the bearer token in constant time and leaves the invalidation call to you, since the exact surface depends on your adapter and Astro version.

Things to know

  • article.html is the body only. The loader hands it to Astro through rendered; use <Content /> rather than set:html where you can.
  • Body images are already in the article HTML. images lists them so you can allowlist hosts and build an image sitemap, not so you render them twice. Show a hero only when image.role === 'featured'.
  • image can be a body image. With no featured image it falls back to the first one in the body, so cards are never blank.
  • The collection is frozen at build, even with output: 'server'. Anything that must be fresh per request has to go through BlogClient.
  • A loader failure fails the build, which leaves your previous deploy live. That is the right default.

Docs and setup guide · All integrations

MIT.

Keywords