JavaScript and TypeScript
@citedseo/feed is the feed and the webhook check as a package: a few functions over fetch, with no dependencies and no key. It runs in Node.js 20+, Cloudflare Workers, Deno, Bun and the browser, and it is what each framework package is built on.
npm install @citedseo/feedpnpm add @citedseo/feedyarn add @citedseo/feedbun add @citedseo/feedRead your articles
Section titled “Read your articles”import { fetchArticle, fetchArticles } from '@citedseo/feed';
const articles = await fetchArticles({ siteId: 'YOUR_SITE_ID' });const article = await fetchArticle({ siteId: 'YOUR_SITE_ID', slug: 'how-to-brew-green-tea' });| Function | Gives | What it reads |
|---|---|---|
fetchArticles(options) |
FeedArticle[], newest first |
The manifest, then every page of the build it names. |
fetchArticle(options) |
FeedArticle | null |
The manifest, then one small file. null when no article has that slug. |
fetchManifest(options) |
FeedManifest |
The manifest alone, to see whether anything changed. |
jsonLd(article) |
string | null |
Nothing. It gives the article’s structured data as text that is safe inside a script tag. |
Options
Section titled “Options”/** What every read takes. */export type FeedOptions = { /** Your site ID. It is the only thing the feed needs. */ siteId: string /** Another origin to read from, for tests. Defaults to https://feeds.citedseo.ai. */ origin?: string /** Your own fetch: a framework's caching one, or a test's. Defaults to the global fetch. */ fetch?: typeof fetch}Pass fetch to read through a cache of your own, such as SvelteKit’s fetch in a load function.
Every function is typed, and the types are exported. They are the ones CitedSEO’s own engine builds the feed against, so they can’t fall behind it.
import type { FeedArticle, FeedManifest, WebhookEvent, WebhookPost } from '@citedseo/feed';The article prints them in full.
Check a webhook
Section titled “Check a webhook”import { webhookHandler } from '@citedseo/feed';
export const POST = webhookHandler({ secret: process.env.CITEDSEO_WEBHOOK_SECRET!, onPost: async (post) => { // post.event is "article.published" or "article.updated"; post.data is the article. },});| Function | What it does |
|---|---|
webhookHandler({ secret, onPost }) |
A whole route: answers 400 to a post that isn’t ours, runs onPost for one that is, answers 204, or 500 when onPost throws so the post is tried again. |
readWebhook(request, secret) |
The verified post as a WebhookPost, or null. |
verifyWebhook(secret, body, header) |
Whether a signature header is ours for a raw body. |
See Verify the signature for what the check does.
Errors
Section titled “Errors”A read that fails throws a CitedSEOError with a code to branch on:
code |
When |
|---|---|
CONFIG |
The site ID or slug is missing. |
NOT_FOUND |
There is no feed for that site ID. |
NETWORK_ERROR |
The feed couldn’t be reached. |
SERVER_ERROR |
The feed answered with an error; status has the code. |
INVALID_RESPONSE |
A file wasn’t the shape it should be. |
An article that isn’t there is not an error: fetchArticle gives null.
Your framework
Section titled “Your framework”Each of these re-exports everything above, so one import covers a site.