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

@nexifi/mdx-blog

v2.1.0

Published

Complete headless blog toolkit — API client, React hooks, SEO components, sitemap, RSS, MDX widgets. Works with Next.js, Remix, Astro, Nuxt, SvelteKit, and any React app.

Readme

@nexifi/mdx-blog

CI npm AI-Ready Showcase License: MIT

Complete headless blog toolkit — API client, React hooks, SEO components, sitemap, RSS, MDX widgets. Works with Next.js, Remix, Astro, Nuxt, SvelteKit, and any React app.

Powering getmax.io — the AI-powered marketing OS by Nexifi. Site, SEO, campaigns, CRM — getMax creates, optimizes and converts 24/7.

Installation

# Core (required)
npm install @nexifi/mdx-blog react react-dom swr

# Framework-specific (install what you use):
npm install next                     # Next.js
npm install @remix-run/react         # Remix
npm install @astrojs/react           # Astro

# Optional — for MDX rendering:
npm install next-mdx-remote @mdx-js/react remark-gfm rehype-highlight

Architecture

@nexifi/mdx-blog
├── Main entry (.)          → React components, hooks, provider, types
├── Server entry (./server) → ContentAPIAdapter, sitemap, RSS, security (Node.js only)
└── MDX entry (./mdx)       → MDX-dependent components (ESM only)

| Import | Usage | |--------|-------| | @nexifi/mdx-blog | Client-side: React components, hooks, provider, i18n, SEO, images | | @nexifi/mdx-blog/server | Server-side: ContentAPIAdapter, sitemap, RSS, security utilities | | @nexifi/mdx-blog/mdx | MDX rendering: BlogArticlePage, MDXProvider, SSG factories (ESM only) |


Features

| Feature | Description | |---------|-------------| | BlogProvider | React context with SWR caching, i18n labels | | 7 React hooks | useArticles, useArticle, useCategories, usePagination, useRelatedArticles, useArticlesByTag, useSearch | | Pre-built pages | BlogListPage (category filters, pagination), BlogArticlePage (MDX + SEO) | | 7 MDX widgets | Newsletter, TableOfContents, AuthorBio, ProductCard, RelatedPosts, StatsBox, FeatureList | | SEO | ArticleSchema / BlogListSchema (JSON-LD), ArticleHead / BlogListHead (meta tags, OG, Twitter Cards) | | Sitemap | XML sitemap generation (runtime SSR + build-time), sitemap index, robots.txt | | RSS/Atom | generateRSSFeed, generateAtomFeed with media extensions | | llms.txt | Build-time llmstxt.org file generation | | Images | BlogImage (responsive, lazy, blur placeholder, srcset), image utilities | | Server adapter | ContentAPIAdapter (API key auth, server-only guard) | | Security | XSS-safe JSON-LD, slug sanitization, fetch timeout, CDATA escaping, XML injection protection | | i18n | All UI strings overridable via labels (French defaults) | | TypeScript | Fully typed, strict mode | | Tree-shakeable | ESM + CJS dual output, sideEffects: false |


CLI — AI-Powered Installation

The package includes an AI agent (powered by @openai/agents) that can autonomously integrate @nexifi/mdx-blog into your project:

# Install with AI agent (requires OPENAI_API_KEY)
npx @nexifi/mdx-blog install

# Dry-run — preview what the agent would do
npx @nexifi/mdx-blog install --dry-run

# Validate an existing integration
npx @nexifi/mdx-blog validate

Commands

| Command | Description | |---------|-------------| | install / setup / init | Run the AI agent to integrate the blog into your project | | validate / check | Static validation of an existing integration (no API key needed) | | help | Show available commands | | version | Show package version |

Install Options

| Flag | Description | |------|-------------| | --dry-run | Preview the integration prompt without running the agent | | --verbose | Show detailed agent activity | | --quiet | Minimal output | | --model <model> | OpenAI model to use (default: gpt-4.1) | | --keep-on-failure | Keep changes even if the agent fails (default: rollback) |

The agent analyzes your project structure (framework, router, styling, conventions), reads your AGENTS.md, and generates a complete integration — including provider setup, API routes, blog pages, Tailwind config, and environment variables.


Supported Frameworks

| Framework | Router | How to use | |-----------|--------|------------| | Next.js | App Router | React components + Route Handlers for API | | Next.js | Pages Router | React components + API routes + SSG factories | | Remix | v2 | React components + loaders/actions for data | | Astro | v4+ | React components via @astrojs/react + API endpoints | | Nuxt | v3 | React components via islands + server routes | | SvelteKit | v2 | Server utilities (sitemap, RSS, ContentAPIAdapter) + custom UI | | Plain React | Any | React components + your own API layer |

The React components (hooks, provider, pages, SEO) work in any React environment. The server utilities (ContentAPIAdapter, sitemap, RSS) work in any Node.js runtime.


Showcase: getmax.io

getmax.io is the reference production implementation of @nexifi/mdx-blog:

  • Framework: Next.js 15 App Router
  • i18n: Locale-prefixed routes (/fr/blog, /en/blog) with [locale] segment
  • Styling: Tailwind CSS v4 with @source directive
  • Features used: Full blog with MDX rendering, JSON-LD SEO, sitemap, RSS/Atom, BlogProvider with French labels, ContentAPIAdapter for server-side data fetching, BlogImage responsive images
  • Architecture: src/app/[locale]/blog/page.tsx (list) + src/app/[locale]/blog/[slug]/page.tsx (article)
  • API routes: src/app/api/blog/route.ts + src/app/api/blog/[slug]/route.ts
  • Live blog: getmax.io/fr/blog

getMax is the AI-powered marketing operating system — site, SEO, campaigns, CRM and analytics in a single console. Built by getMax.


Quick Start

1. Wrap your app with BlogProvider

import { BlogProvider } from '@nexifi/mdx-blog';

<BlogProvider
  config={{
    endpoints: {
      articles: '/api/blog',
      article: '/api/blog/:slug',
      categories: '/api/blog/categories',
    },
  }}
  labels={{
    home: 'Home',
    blog: 'Blog',
    readMore: 'Read more',
    backToBlog: 'Back to blog',
  }}
>
  {children}
</BlogProvider>

2. Create API routes (server-side only)

ContentAPIAdapter must ONLY be used server-side. It throws if instantiated in the browser.

Next.js App Router

// app/api/blog/route.ts
import { NextResponse } from 'next/server';
import { ContentAPIAdapter } from '@nexifi/mdx-blog/server';

const adapter = new ContentAPIAdapter({
  apiKey: process.env.CONTENT_API_KEY!,
  baseUrl: process.env.CONTENT_API_URL,
});

export async function GET() {
  const articles = await adapter.getAllArticles();
  return NextResponse.json(articles, {
    headers: { 'Cache-Control': 'public, s-maxage=300, stale-while-revalidate=600' },
  });
}

Next.js Pages Router

// pages/api/blog/index.ts
import type { NextApiRequest, NextApiResponse } from 'next';
import { ContentAPIAdapter } from '@nexifi/mdx-blog/server';

const adapter = new ContentAPIAdapter({
  apiKey: process.env.CONTENT_API_KEY!,
  baseUrl: process.env.CONTENT_API_URL,
});

export default async function handler(req: NextApiRequest, res: NextApiResponse) {
  const articles = await adapter.getAllArticles();
  res.setHeader('Cache-Control', 'public, s-maxage=300, stale-while-revalidate=600');
  res.json(articles);
}

Remix

// app/routes/api.blog.tsx
import { json } from '@remix-run/node';
import { ContentAPIAdapter } from '@nexifi/mdx-blog/server';

const adapter = new ContentAPIAdapter({
  apiKey: process.env.CONTENT_API_KEY!,
  baseUrl: process.env.CONTENT_API_URL,
});

export async function loader() {
  const articles = await adapter.getAllArticles();
  return json(articles, {
    headers: { 'Cache-Control': 'public, s-maxage=300, stale-while-revalidate=600' },
  });
}

Astro

// src/pages/api/blog.ts
import type { APIRoute } from 'astro';
import { ContentAPIAdapter } from '@nexifi/mdx-blog/server';

const adapter = new ContentAPIAdapter({
  apiKey: import.meta.env.CONTENT_API_KEY,
  baseUrl: import.meta.env.CONTENT_API_URL,
});

export const GET: APIRoute = async () => {
  const articles = await adapter.getAllArticles();
  return new Response(JSON.stringify(articles), {
    headers: { 'Content-Type': 'application/json' },
  });
};

SvelteKit

// src/routes/api/blog/+server.ts
import { json } from '@sveltejs/kit';
import { ContentAPIAdapter } from '@nexifi/mdx-blog/server';
import { CONTENT_API_KEY, CONTENT_API_URL } from '$env/static/private';

const adapter = new ContentAPIAdapter({
  apiKey: CONTENT_API_KEY,
  baseUrl: CONTENT_API_URL,
});

export async function GET() {
  const articles = await adapter.getAllArticles();
  return json(articles);
}

3. Blog list page

import { BlogListPage, BlogListHead, BlogListSchema } from '@nexifi/mdx-blog';

const seoConfig = { siteUrl: 'https://getmax.io', siteName: 'getMax', blogPath: '/blog' };

export default function Blog() {
  return (
    <>
      <BlogListHead config={seoConfig} title="Blog" description="Our latest articles" />
      <BlogListSchema config={seoConfig} articles={[]} />
      <BlogListPage title="Blog" blogPath="/blog" perPage={9} />
    </>
  );
}

4. Article page

Next.js App Router (Server Component — recommended)

Do NOT use next-mdx-remote/rsc in Next.js 15+ App Router — it causes uncatchable RSC streaming errors. Use renderMarkdown instead:

// app/blog/[slug]/page.tsx
import { ContentAPIAdapter, renderMarkdown } from '@nexifi/mdx-blog/server';
import { ArticleHead, ArticleSchema, ArticleLayout } from '@nexifi/mdx-blog';

const adapter = new ContentAPIAdapter({
  apiKey: process.env.CONTENT_API_KEY!,
  baseUrl: process.env.CONTENT_API_URL,
});

export default async function ArticlePage({ params }: { params: Promise<{ slug: string }> }) {
  const { slug } = await params;
  const article = await adapter.getArticleBySlug(slug);
  if (!article) notFound();
  const html = await renderMarkdown(article.content || '');
  return (
    <ArticleLayout article={article}>
      <div className="prose prose-invert" dangerouslySetInnerHTML={{ __html: html }} />
    </ArticleLayout>
  );
}

Next.js Pages Router (SSG)

// pages/blog/[slug].tsx
import { BlogArticlePage, createGetStaticPaths, createGetStaticProps } from '@nexifi/mdx-blog/mdx';

export const getStaticPaths = createGetStaticPaths({ articlesEndpoint: '/api/blog' });
export const getStaticProps = createGetStaticProps({
  articlesEndpoint: '/api/blog',
  articleEndpoint: '/api/blog/:slug',
  revalidateSeconds: 3600,
  relatedCount: 3,
});

export default BlogArticlePage;

5. Tailwind CSS

Required: Install the typography plugin for prose styling of article content:

npm install @tailwindcss/typography

Tailwind v4 — add to your main CSS:

@import "tailwindcss";
@plugin "@tailwindcss/typography";
@source "../../node_modules/@nexifi/mdx-blog/dist";

Tailwind v3 — add to tailwind.config.js:

plugins: [
  require('@tailwindcss/typography'),
],
content: [
  './node_modules/@nexifi/mdx-blog/**/*.{js,ts,jsx,tsx}',
],

6. Environment Variables

CONTENT_API_KEY=ak_xxxxxxxxxxxxx
CONTENT_API_URL=https://api-growthos.nexifi.com/api/contentmaster/projects/<PROJECT_ID>/articles
NEXT_PUBLIC_SITE_URL=https://getmax.io

i18n / Labels

All UI text is configurable via BlogProvider's labels prop. French defaults are built-in.

<BlogProvider
  config={config}
  labels={{
    home: 'Home', blog: 'Blog', backToBlog: 'Back to blog',
    readMore: 'Read more', readArticle: 'Read article',
    relatedArticles: 'Related articles',
    tags: 'Tags:', share: 'Share:', minRead: 'min read',
    allArticles: 'All articles',
    previous: 'Previous', next: 'Next',
    pageXofY: (c, t) => `Page ${c} of ${t}`,
    articleCount: (n) => `${n} article${n > 1 ? 's' : ''}`,
    blogSubtitle: 'Discover our latest articles',
    loading: 'Loading...', noArticlesFound: 'No articles found.',
    newsletterTitle: 'Subscribe to our newsletter',
    tableOfContents: 'Table of Contents',
    // ... see Labels interface for all 30+ keys
  }}
/>

SEO

JSON-LD Structured Data

import { ArticleSchema, BlogListSchema } from '@nexifi/mdx-blog';

<ArticleSchema article={article} config={{
  siteUrl: 'https://getmax.io', siteName: 'getMax', blogPath: '/blog',
}} />

<BlogListSchema config={schemaConfig} articles={articles} />

Meta Tags (Open Graph, Twitter Cards)

import { ArticleHead, BlogListHead } from '@nexifi/mdx-blog';

<ArticleHead article={article} config={{
  siteUrl: 'https://getmax.io', siteName: 'getMax',
  twitterHandle: '@getmaxio', blogPath: '/blog',
}} />

Build-Time Sitemap + robots.txt + llms.txt

import { generateBuildTimeSEO } from '@nexifi/mdx-blog/server';

await generateBuildTimeSEO({
  siteUrl: 'https://getmax.io',
  apiKey: process.env.CONTENT_API_KEY,
  blogPath: '/blog',
  outputDir: './public',
  staticPages: [
    { path: '/', priority: 1.0, changefreq: 'daily' },
    { path: '/fr', priority: 1.0, changefreq: 'daily' },
    { path: '/fr/blog', priority: 0.9, changefreq: 'daily' },
    { path: '/fr/integrations', priority: 0.8, changefreq: 'weekly' },
    { path: '/fr/mcp', priority: 0.8, changefreq: 'weekly' },
  ],
  llmsConfig: {
    name: 'getMax', description: 'Votre croissance marketing en pilote automatique. Site, SEO, campagnes, CRM : getMax crée, optimise et convertit 24/7.',
    services: ['Site web & Landing Pages', 'SEO & Référencement', 'CRM & Nurturing', 'Publicité & Acquisition', 'Analytics & Reporting', 'Marketing Automation'],
  },
});

SSR llms.txt (Pages Router)

// pages/llms.txt.tsx
import { LlmsPage, createLlmsServerSideProps } from '@nexifi/mdx-blog/server';

export default LlmsPage;
export const getServerSideProps = createLlmsServerSideProps({
  siteUrl: 'https://getmax.io',
  blogPath: '/blog',
  llmsConfig: {
    name: 'getMax',
    description: 'AI-powered marketing operating system.',
    contact: { email: '[email protected]' },
    services: [
      { title: 'SEO', url: 'https://getmax.io/seo', description: 'AI-driven SEO' },
    ],
  },
});

// pages/llms-full.txt.tsx — same but with full: true

App Router / Remix / Astro — llms.txt

// app/llms.txt/route.ts (Next.js App Router)
import { generateLlmsTxt, ContentAPIAdapter } from '@nexifi/mdx-blog/server';

const adapter = new ContentAPIAdapter({ apiKey: process.env.CONTENT_API_KEY! });

export async function GET() {
  const articles = await adapter.getAllArticles();
  const { llmsTxt } = generateLlmsTxt(
    { name: 'getMax', description: 'AI-powered marketing OS.' },
    articles, 'https://getmax.io', '/blog',
  );
  return new Response(llmsTxt, {
    headers: { 'Content-Type': 'text/plain; charset=utf-8', 'Cache-Control': 'public, max-age=86400' },
  });
}

RSS / Atom Feeds

import { generateRSSFeed, generateAtomFeed } from '@nexifi/mdx-blog/server';

const rss = generateRSSFeed(articles, {
  siteUrl: 'https://getmax.io', title: 'Blog getMax',
  description: 'Votre croissance marketing en pilote automatique — articles SEO, acquisition et CRM', blogPath: '/blog', language: 'fr',
});

API Reference

Provider & Context

| Export | Type | Description | |--------|------|-------------| | BlogProvider | Component | Root context provider — config + labels | | useBlogClient() | Hook | Access the BlogApiClient instance | | useLabels() | Hook | Access the current labels (i18n) |

Hooks

| Hook | Returns | Description | |------|---------|-------------| | useArticles() | { articles, isLoading, error, refresh } | All published articles (SWR cached) | | useArticle(slug) | { article, isLoading, error, refresh } | Single article by slug | | useCategories() | { categories, isLoading, error, refresh } | Available categories | | useRelatedArticles(slug, limit?) | { relatedArticles, isLoading, error } | Related articles (same category/tags) | | usePagination(options?) | { items, pagination, setPage, setCategory } | Client-side pagination + category filter | | useArticlesByTag(tag) | { articles, isLoading, error } | Articles by tag | | useSearch(query, options?) | { results, isSearching, totalResults } | Client-side full-text search |

Page Components

| Component | Key Props | Description | |-----------|-----------|-------------| | BlogListPage | title, blogPath, perPage, showCategories, ImageComponent | Full blog listing with category filters + pagination | | BlogArticlePage | article, mdxSource, relatedArticles, seoConfig | Full article page with MDX rendering (via ./mdx) | | ArticleLayout | article, children, showBreadcrumb, showShareButtons, showTags | Article layout wrapper | | ArticlePlaceholder | category, icon | Gradient placeholder when article has no image |

SEO Components

| Component | Key Props | Description | |-----------|-----------|-------------| | ArticleHead | article, config: SEOConfig | <head> meta tags (OG, Twitter, canonical) | | BlogListHead | config: SEOConfig, title, description | Blog index meta tags | | ArticleSchema | article, config: SchemaConfig | JSON-LD: BlogPosting + BreadcrumbList + WebPage | | BlogListSchema | config: SchemaConfig, articles | JSON-LD: CollectionPage + ItemList |

MDX Components (@nexifi/mdx-blog/mdx)

| Component | Description | |-----------|-------------| | BlogArticlePage | Full article page with MDX rendering | | MDXProvider | Wraps MDX content with styled HTML elements + all widgets | | ArticleCTA | Call-to-action block (3 variants: primary, secondary, success) | | Alert | Callout box (info, warning, success, danger, tip) | | createGetStaticPaths(config) | SSG factory for Next.js getStaticPaths | | createGetStaticProps(config) | SSG factory for Next.js getStaticProps with MDX serialization |

Widgets (usable in MDX content)

| Widget | Key Props | Description | |--------|-----------|-------------| | Newsletter | onSubmit, apiEndpoint, variant | Email signup (inline, card, minimal) | | TableOfContents | maxDepth, collapsible | Auto-generated TOC with active heading tracking | | AuthorBio | name, bio, links, variant | Author card (compact or full) | | ProductCard | name, price, rating, link | E-commerce product card | | RelatedPosts | posts: Article[], maxPosts | Related articles block | | StatsBox | stats: StatItem[], variant | Statistics display (horizontal, vertical, grid) | | FeatureList | features, variant | Feature/advantage list (list, grid, compact) |

Image

| Export | Type | Description | |--------|------|-------------| | BlogImage | Component | Responsive image with lazy load, blur, srcset, error fallback | | resolveImageUrl | Function | Resolve relative images to absolute URLs | | generateSrcSet | Function | Generate srcset string | | generateSizes | Function | Generate sizes attribute | | computeHeight | Function | Compute height from width + aspect ratio | | buildImageObject | Function | Build JSON-LD ImageObject | | isExternalImage | Function | Check if URL is external |

Server Adapter (@nexifi/mdx-blog/server)

| Export | Description | |--------|-------------| | ContentAPIAdapter | Server-only HTTP client with API key auth (throws in browser) | | .getAllArticles() | Fetch all published articles | | .getArticleBySlug(slug) | Fetch single article | | .updateArticleStatus(id, status) | Update article status | | .transformArticle(data) | Transform raw API data to Article |

Sitemap & SEO Utilities (@nexifi/mdx-blog/server)

| Export | Description | |--------|-------------| | generateSitemap(articles, config) | Generate XML sitemap from articles | | buildSitemapXML(entries) | Build XML from custom entries | | generateSitemapIndex(sitemaps, siteUrl) | Sitemap index for large sites | | getArticleSitemapEntries(articles, blogPath?) | Entries for next-sitemap integration | | generateRobotsTxt(siteUrl, options?) | Generate robots.txt | | generateStaticSitemap(config) | Build-time sitemap with article fetch | | generateSitemapOnBuild(options) | Write sitemap.xml to disk | | generateBuildTimeSEO(config) | Full pipeline: sitemap + robots + llms.txt | | generateLlmsTxt(config, articles, baseUrl, blogPath) | Generate llms.txt + llms-full.txt content | | LlmsPage | SSR page component for llms.txt (Pages Router) | | createLlmsServerSideProps(config) | Factory for llms.txt getServerSideProps | | generateRSSFeed(articles, config) | RSS 2.0 XML | | generateAtomFeed(articles, config) | Atom 1.0 XML |

Security

| Export | Description | |--------|-------------| | safeJsonLd(data) | XSS-safe JSON serialization for <script type="application/ld+json"> | | sanitizeSlug(slug) | Validate slug (alphanumeric + hyphens, no path traversal) | | fetchWithTimeout(url, options, timeout?) | Fetch with configurable timeout (default: 10s) | | escapeXml(str) | Escape XML special characters |


Security

| Protection | Implementation | |------------|----------------| | XSS in JSON-LD | safeJsonLd() escapes </script>, <!--, -->, \u2028, \u2029 | | Slug injection | sanitizeSlug() validates alphanumeric + hyphens, blocks path traversal | | Fetch timeout | fetchWithTimeout() prevents hanging requests (default: 10s) | | API key exposure | ContentAPIAdapter throws if instantiated in browser | | Auth header warning | BlogApiClient warns if Authorization header used client-side | | XML injection | escapeXml() applied to all sitemap interpolation | | CDATA breakout | RSS/Atom feeds escape ]]> sequences in CDATA sections |


License

MIT © Powering getMax