Skip to content
Docs

React Router

@citedseo/react-router gives your loaders the feed and your routes their meta tags. Two routes with server loaders, in React Router v7’s framework mode. It needs React Router 7.9 or later, the first version that escapes JSON-LD in meta.

Hand this to your coding agent

Read https://developer.citedseo.ai/llms-full.txt.
My CitedSEO site ID is <YOUR_SITE_ID>.
Install @citedseo/react-router. Add blog._index and blog.$slug routes with loaders and meta in this React Router v7 app, reading my articles with that package.
The feed needs no key. Use only the functions and addresses that file lists.
Terminal window
npm install @citedseo/react-router
  • .env
  • Directoryapp
    • routes.ts your routes
    • Directoryroutes
      • blog._index.tsx the index
      • blog.$slug.tsx one article
      • api.citedseo.ts the webhook
  1. Set your site ID.

    .env
    CITEDSEO_SITE_ID=YOUR_SITE_ID
  2. Add the index.

    app/routes/blog._index.tsx
    import { fetchArticles } from '@citedseo/react-router';
    import { Link } from 'react-router';
    import type { Route } from './+types/blog._index';
    export async function loader() {
    const articles = await fetchArticles({ siteId: process.env.CITEDSEO_SITE_ID! });
    return {
    articles: articles.map(({ id, slug, title, description }) => ({ id, slug, title, description })),
    };
    }
    export default function Blog({ loaderData }: Route.ComponentProps) {
    return (
    <ul>
    {loaderData.articles.map((article) => (
    <li key={article.id}>
    <Link to={`/blog/${article.slug}`}>{article.title}</Link>
    <p>{article.description}</p>
    </li>
    ))}
    </ul>
    );
    }
  3. Add the article route. articleMeta gives the title, description, canonical link, Open Graph tags and JSON-LD.

    app/routes/blog.$slug.tsx
    import { articleMeta, fetchArticle } from '@citedseo/react-router';
    import { data } from 'react-router';
    import type { Route } from './+types/blog.$slug';
    export async function loader({ params }: Route.LoaderArgs) {
    const article = await fetchArticle({
    siteId: process.env.CITEDSEO_SITE_ID!,
    slug: params.slug,
    });
    if (!article) throw data(null, { status: 404 });
    return { article };
    }
    export function meta({ data }: Route.MetaArgs) {
    return data ? articleMeta(data.article) : [];
    }
    export default function ArticlePage({ loaderData }: Route.ComponentProps) {
    const { article } = loaderData;
    return (
    <article>
    <h1>{article.title}</h1>
    {article.cover && <img src={article.cover.src} alt={article.cover.alt} />}
    <div dangerouslySetInnerHTML={{ __html: article.html }} />
    </article>
    );
    }
  4. Register the routes. If your app lists its routes in app/routes.ts, add these three. With @react-router/fs-routes and flatRoutes(), the file names above are found without it.

    app/routes.ts
    import { type RouteConfig, route } from '@react-router/dev/routes';
    export default [
    // …your other routes
    route('blog', 'routes/blog._index.tsx'),
    route('blog/:slug', 'routes/blog.$slug.tsx'),
    route('api/citedseo', 'routes/api.citedseo.ts'),
    ] satisfies RouteConfig;

Each request reads the manifest, which is cached for a minute, so a new article shows within a minute with no webhook at all.

If you add your own cache in front of your pages, clear it from a resource route. webhookHandler checks the signature for you:

app/routes/api.citedseo.ts
import { webhookHandler } from '@citedseo/react-router';
import type { Route } from './+types/api.citedseo';
const handle = webhookHandler({
secret: process.env.CITEDSEO_WEBHOOK_SECRET!,
onPost: async (post) => {
// Clear your cache, or store post.data.
},
});
export const action = ({ request }: Route.ActionArgs) => handle(request);

See Update the moment you publish.

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.