npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@rankveo/astro

v0.2.3

Published

Run your rankveo blog on an Astro site. SEO, GEO and AEO content, served from your own domain.

Downloads

148

Readme

@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/blog.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.