Next.js
@citedseo/next reads your feed through Next.js’s own cache, and gives each article page its metadata. A blog index and article pages in the App Router, statically rendered and refreshed when you publish.
Hand this to your coding agent
Read https://developer.citedseo.ai/llms-full.txt. My CitedSEO site ID is <YOUR_SITE_ID>. Install @citedseo/next. Add a /blog index and /blog/[slug] article pages, with generateMetadata and the article's JSON-LD in this Next.js App Router app, reading my articles with that package. The feed needs no key. Use only the functions and addresses that file lists.
Install
Section titled “Install”npm install @citedseo/nextpnpm add @citedseo/nextyarn add @citedseo/nextbun add @citedseo/nextWhat you’ll add
Section titled “What you’ll add”- .env.local
Directorylib
- citedseo.ts the feed
Directoryapp
Directoryblog
- page.tsx the index
Directory[slug]
- page.tsx one article
Directoryapi
Directorycitedseo
- route.ts the webhook
-
Set your site ID.
.env.local CITEDSEO_SITE_ID=YOUR_SITE_ID -
Make the reader, once.
lib/citedseo.ts import { createCitedSEO } from '@citedseo/next';export const citedseo = createCitedSEO({ siteId: process.env.CITEDSEO_SITE_ID! }); -
Add the index.
app/blog/page.tsx import Link from 'next/link';import { citedseo } from '@/lib/citedseo';export default async function Blog() {const articles = await citedseo.getArticles();return (<ul>{articles.map((article) => (<li key={article.id}><Link href={`/blog/${article.slug}`}>{article.title}</Link><p>{article.description}</p></li>))}</ul>);} -
Add the article page, with its metadata and structured data.
app/blog/[slug]/page.tsx import type { Metadata } from 'next';import { notFound } from 'next/navigation';import { articleMetadata, jsonLd } from '@citedseo/next';import { citedseo } from '@/lib/citedseo';type Props = { params: Promise<{ slug: string }> };export const generateStaticParams = citedseo.generateStaticParams;export async function generateMetadata({ params }: Props): Promise<Metadata> {const article = await citedseo.getArticle((await params).slug);return article ? articleMetadata(article) : {};}export default async function ArticlePage({ params }: Props) {const article = await citedseo.getArticle((await params).slug);if (!article) notFound();const data = jsonLd(article);return (<article><h1>{article.title}</h1>{article.cover && <img src={article.cover.src} alt={article.cover.alt} />}<div dangerouslySetInnerHTML={{ __html: article.html }} />{data && <script type="application/ld+json" dangerouslySetInnerHTML={{ __html: data }} />}</article>);} -
Refresh when you publish.
webhookHandlerchecks the signature, answers400to a post that isn’t ours, and runsonPostfor one that is.app/api/citedseo/route.ts import { revalidatePath } from 'next/cache';import { webhookHandler } from '@citedseo/next';export const POST = webhookHandler({secret: process.env.CITEDSEO_WEBHOOK_SECRET!,onPost: () => revalidatePath('/blog', 'layout'),});Then add the endpoint in the app, at
https://your-site.com/api/citedseo, and put its secret inCITEDSEO_WEBHOOK_SECRET.
Options
Section titled “Options”createCitedSEO takes your site ID and two options for the cache:
| Option | Default | What it does |
|---|---|---|
siteId |
Your site ID. | |
revalidate |
60 |
Seconds Next.js keeps what it read. false keeps it until you revalidate; 0 reads on every request. The manifest is never kept longer than five minutes. |
tags |
['citedseo'] |
Cache tags on every read, for revalidateTag. |
With the default, a new article shows within a minute or so even with no webhook. With the webhook you can raise it or set false: the articles’ files are then kept, and the package still re-reads the manifest at least every five minutes, so a missed webhook heals itself and a page never follows a manifest to a build that has been removed.
What you get back
Section titled “What you get back”getArticles() gives FeedArticle[] and getArticle(slug) gives FeedArticle | null. The type is exported from the package, and The article lists every field.
Keep the address the same
Section titled “Keep the address the same”Each article’s url is where CitedSEO expects it to live, and its structured data and canonical address say so. If your blog isn’t at /blog, set the site’s blog path in the app’s settings to match your route. See SEO for your article pages.