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

@content-streamline/nextjs

v0.0.12

Published

Next.js library for Content Streamline API with reusable components

Readme

@content-streamline/nextjs

Next.js library for Content Streamline API with reusable, responsive components.

Features

  • 🚀 Easy Setup - Configure via environment variables
  • 📦 Reusable Components - Pre-built React components for posts
  • 💾 Next.js Native Caching - Uses fetch() with revalidate for ISR support
  • 🔄 On-Demand Revalidation - Cache tags for targeted refresh via revalidateTag()
  • 🎨 Customizable - Responsive components with configurable props
  • 📱 Mobile-Friendly - Built-in responsive design
  • 🔧 TypeScript - Full TypeScript support
  • 🏷️ Platform Filtering - Filter posts by platform (blog, linkedin, x, etc.)

Installation

npm install @content-streamline/nextjs
# or
bun add @content-streamline/nextjs

Note: React and Next.js are peer dependencies. Make sure you have them installed in your Next.js project.

Quick Start

1. Configure Environment Variables

Create a .env.local file in your Next.js project:

CONTENT_STREAMLINE_API_BASE_URL=https://your-api-domain.com
CONTENT_STREAMLINE_API_ACCESS_TOKEN=your_access_token_here

Or use shorter names:

API_BASE_URL=https://your-api-domain.com
API_ACCESS_TOKEN=your_access_token_here

2. Import Styles

In your app/layout.tsx or pages/_app.tsx:

import '@content-streamline/nextjs/components/styles.css';

3. Use in Server Components (App Router)

Blog Page with Featured Posts (app/blog/page.tsx):

import { getAllPosts } from '@content-streamline/nextjs/server';
import { LatestPosts, PostsList } from '@content-streamline/nextjs/components';

export const revalidate = 300; // Revalidate every 5 minutes (ISR)

export default async function BlogPage() {
  const posts = await getAllPosts({ platform: 'blog' });
  
  return (
    <div>
      <h1>Blog</h1>
      
      {/* Featured section with latest 3 posts */}
      <section>
        <h2>Latest Posts</h2>
        <LatestPosts posts={posts} count={3} baseUrl="/blog" />
      </section>
      
      {/* Remaining posts in vertical list */}
      <section>
        <h2>All Posts</h2>
        <PostsList posts={posts} skipFirst={3} baseUrl="/blog" />
      </section>
    </div>
  );
}

Post Detail Page (app/blog/[id]/[slug]/page.tsx):

import { getPostBySlug } from '@content-streamline/nextjs/server';
import { PostDetail } from '@content-streamline/nextjs/components';
import { notFound } from 'next/navigation';

export async function generateStaticParams() {
  const { getAllPosts } = await import('@content-streamline/nextjs/server');
  const posts = await getAllPosts({ platform: 'blog' });
  
  return posts.flatMap(post => 
    post.post_translations
      .filter(t => t.slug)
      .map(translation => ({
        id: post.id.toString(),
        slug: translation.slug,
      }))
  );
}

export default async function PostPage({
  params,
}: {
  params: { id: string; slug: string };
}) {
  const post = await getPostBySlug(params.slug, { platform: 'blog' });
  
  if (!post) {
    notFound();
  }
  
  return (
    <PostDetail post={post} backUrl="/blog" />
  );
}

4. Use in Pages Router

Blog Page (pages/blog/index.tsx):

import { getAllPosts } from '@content-streamline/nextjs/server';
import { LatestPosts, PostsList } from '@content-streamline/nextjs/components';
import type { GetStaticProps } from 'next';
import type { Post } from '@content-streamline/nextjs';

export const getStaticProps: GetStaticProps = async () => {
  const posts = await getAllPosts({ platform: 'blog' });
  
  return {
    props: { posts },
    revalidate: 300, // Revalidate every 5 minutes
  };
};

export default function BlogPage({ posts }: { posts: Post[] }) {
  return (
    <div>
      <h1>Blog</h1>
      <LatestPosts posts={posts} count={3} baseUrl="/blog" />
      <PostsList posts={posts} skipFirst={3} baseUrl="/blog" />
    </div>
  );
}

Post Detail Page (pages/blog/[id]/[slug].tsx):

import { getPostBySlug, getAllPosts } from '@content-streamline/nextjs/server';
import { PostDetail } from '@content-streamline/nextjs/components';
import type { GetStaticProps, GetStaticPaths } from 'next';
import type { PostDetailResponse } from '@content-streamline/nextjs';

export const getStaticPaths: GetStaticPaths = async () => {
  const posts = await getAllPosts({ platform: 'blog' });
  
  const paths = posts.flatMap(post => 
    post.post_translations
      .filter(t => t.slug)
      .map(translation => ({
        params: {
          id: post.id.toString(),
          slug: translation.slug,
        },
      }))
  );
  
  return { paths, fallback: 'blocking' };
};

export const getStaticProps: GetStaticProps = async ({ params }) => {
  const post = await getPostBySlug(params!.slug as string, { platform: 'blog' });
  
  if (!post) {
    return { notFound: true };
  }
  
  return {
    props: { post },
    revalidate: 300,
  };
};

export default function PostPage({ post }: { post: PostDetailResponse }) {
  return <PostDetail post={post} backUrl="/blog" />;
}

API Reference

Server Functions

getAllPosts(options?)

Get all posts with Next.js caching (ISR-compatible).

import { getAllPosts } from '@content-streamline/nextjs/server';

const posts = await getAllPosts({
  platform: 'blog',       // Filter by platform (default: undefined = all)
  revalidate: 300,        // Cache TTL in seconds (default: 300 = 5 min)
  forceRefresh: false,    // Bypass cache (default: false)
  tags: ['custom-tag'],   // Additional cache tags for revalidateTag()
});

Platform values: 'blog', 'x', 'linkedin', 'facebook', 'instagram', 'other'

getPost(id, options?)

Get a single post by ID.

import { getPost } from '@content-streamline/nextjs/server';

const post = await getPost(123, {
  language: 'en',
  contentFormat: 'markdown', // or 'html'
  revalidate: 300,           // Cache TTL in seconds (default: 300)
  forceRefresh: false,
  tags: ['custom-tag'],
});

getPostBySlug(slug, options?)

Get a post by slug.

import { getPostBySlug } from '@content-streamline/nextjs/server';

const post = await getPostBySlug('my-post-slug', {
  language: 'en',
  contentFormat: 'markdown',
  platform: 'blog',
  revalidate: 300,
  forceRefresh: false,
});

getCacheTags()

Get cache tag names for use with revalidateTag().

import { getCacheTags } from '@content-streamline/nextjs/server';

const tags = getCacheTags();
// tags.all        → 'content-streamline' (all content)
// tags.postsList  → 'posts-list' (post listings)
// tags.post(123)  → 'post-123' (specific post)

Components

<LatestPosts />

Display featured/hero posts with the first post highlighted. Perfect for homepage or blog landing sections.

import { LatestPosts } from '@content-streamline/nextjs/components';

<LatestPosts
  posts={posts}
  count={3}                // Number of posts to show (default: 3)
  baseUrl="/blog"          // Base URL for post links
  platform="blog"          // Filter by platform (default: 'blog')
  language="en"            // Language for translations (default: 'en')
  className="custom-class" // Custom container class
  cardClassName="custom"   // Custom card class
  showDate={true}          // Show post date (default: true)
  showExcerpt={true}       // Show post excerpt (default: true)
  showImages={true}        // Show post images (default: true)
  renderTitle={(post, translation) => <CustomTitle />}    // Custom title renderer
  renderExcerpt={(post, translation) => <CustomExcerpt />} // Custom excerpt renderer
/>

Layout behavior:

  • First post is displayed as a featured card (larger, with image on left)
  • Remaining posts are displayed in a responsive grid below
  • Fully responsive - stacks vertically on mobile

<PostsList />

Display posts in a vertical list with thumbnails on the left. Ideal for archive pages or "more posts" sections.

import { PostsList } from '@content-streamline/nextjs/components';

<PostsList
  posts={posts}
  baseUrl="/blog"          // Base URL for post links
  platform="blog"          // Filter by platform (default: 'blog')
  skipFirst={3}            // Skip first N posts (default: 0)
  language="en"            // Language for translations (default: 'en')
  className="custom-class" // Custom container class
  itemClassName="custom"   // Custom item class
  showDate={true}          // Show post date (default: true)
  showExcerpt={true}       // Show post excerpt (default: true)
  showImages={true}        // Show post images (default: true)
  renderTitle={(post, translation) => <CustomTitle />}    // Custom title renderer
  renderExcerpt={(post, translation) => <CustomExcerpt />} // Custom excerpt renderer
/>

Layout behavior:

  • Vertical list with image thumbnail on the left
  • Title and excerpt on the right
  • Compact design for browsing many posts
  • skipFirst prop allows combining with LatestPosts to avoid duplicates

<PostDetail />

Display a single post with full content.

import { PostDetail } from '@content-streamline/nextjs/components';

<PostDetail
  post={post}
  backUrl="/blog"          // URL for back link
  language="en"            // Language for translation (default: 'en')
  className="custom-class" // Custom container class
  showBackLink={true}      // Show back link (default: true)
  showDate={true}          // Show post date (default: true)
  showType={true}          // Show post type badge (default: true)
  showImage={true}         // Show featured image (default: true)
  showGallery={true}       // Show gallery images (default: true)
  renderContent={(content) => <MarkdownRenderer content={content} />} // Custom content renderer
  renderTitle={(post, translation) => <CustomTitle />} // Custom title renderer
/>

Common Patterns

Featured Posts + Archive List

Combine LatestPosts and PostsList for a complete blog layout:

import { getAllPosts } from '@content-streamline/nextjs/server';
import { LatestPosts, PostsList } from '@content-streamline/nextjs/components';

export default async function BlogPage() {
  const posts = await getAllPosts({ platform: 'blog' });
  
  return (
    <main>
      {/* Hero section with 3 featured posts */}
      <section className="hero">
        <LatestPosts posts={posts} count={3} baseUrl="/blog" />
      </section>
      
      {/* Archive list (skip the 3 featured posts) */}
      <section className="archive">
        <h2>More Articles</h2>
        <PostsList posts={posts} skipFirst={3} baseUrl="/blog" />
      </section>
    </main>
  );
}

Multi-Platform Content

Display content from different platforms:

// Blog posts
const blogPosts = await getAllPosts({ platform: 'blog' });

// LinkedIn posts
const linkedinPosts = await getAllPosts({ platform: 'linkedin' });

// All posts (no filter)
const allPosts = await getAllPosts();

Homepage with Single Featured Post

<LatestPosts posts={posts} count={1} baseUrl="/blog" />

Customization

Styling

Components use CSS classes with the content-streamline- prefix. You can override styles:

/* Override latest posts card */
.content-streamline-latest-card {
  border-radius: 20px;
  box-shadow: 0 8px 24px rgba(0, 0, 0, 0.12);
}

/* Override featured card */
.content-streamline-latest-card--featured {
  background: linear-gradient(135deg, #f5f7fa 0%, #c3cfe2 100%);
}

/* Override list items */
.content-streamline-list-item {
  border-left: 4px solid #3b82f6;
}

/* Override post title */
.content-streamline-latest-title a,
.content-streamline-list-title a {
  color: #your-brand-color;
}

Custom Rendering

Use render props for complete control:

<LatestPosts
  posts={posts}
  renderTitle={(post, translation) => (
    <h2 className="my-custom-title">
      <span className="category">{post.content_type}</span>
      {translation.title}
    </h2>
  )}
  renderExcerpt={(post, translation) => (
    <p className="my-custom-excerpt">
      {translation.meta_description?.slice(0, 100)}...
    </p>
  )}
/>

Markdown Rendering

For proper markdown rendering, use a library like react-markdown:

import ReactMarkdown from 'react-markdown';
import { PostDetail } from '@content-streamline/nextjs/components';

<PostDetail
  post={post}
  renderContent={(content) => (
    <ReactMarkdown>{content}</ReactMarkdown>
  )}
/>

Caching

The library uses Next.js native fetch caching for optimal performance with ISR (Incremental Static Regeneration).

How It Works

  • Default TTL: 5 minutes (300 seconds)
  • Automatic revalidation: Next.js refreshes cache in background after TTL expires
  • Cache tags: Each request is tagged for targeted invalidation
  • Works with static generation: Pages can be statically generated at build time

Cache Tags

Every request is tagged for granular cache control:

| Tag | Description | |-----|-------------| | content-streamline | All content from this library | | posts-list | Post listing requests | | post-{id} | Individual post by ID (e.g., post-123) |

Automatic Revalidation (Time-Based)

Cache automatically refreshes after the TTL expires:

// 5 minute cache (default)
const posts = await getAllPosts();

// 1 minute cache
const posts = await getAllPosts({ revalidate: 60 });

// 1 hour cache
const posts = await getAllPosts({ revalidate: 3600 });

// No caching (always fresh)
const posts = await getAllPosts({ forceRefresh: true });

On-Demand Revalidation

Invalidate cache instantly when content changes (e.g., from a webhook):

// app/api/revalidate/route.ts
import { revalidateTag } from 'next/cache';
import { getCacheTags } from '@content-streamline/nextjs';

export async function POST(request: Request) {
  const { postId } = await request.json();
  
  // Revalidate specific post
  if (postId) {
    revalidateTag(getCacheTags().post(postId));
  }
  
  // Or revalidate all posts
  revalidateTag(getCacheTags().postsList);
  
  return Response.json({ revalidated: true });
}

Static Generation with ISR

For fully static pages with background revalidation:

// app/blog/page.tsx
export const revalidate = 300; // Revalidate page every 5 minutes

export default async function BlogPage() {
  const posts = await getAllPosts({ platform: 'blog' });
  return <PostsList posts={posts} />;
}

TypeScript

Full TypeScript support is included:

import type { 
  Post, 
  PostDetailResponse,
  GetAllPostsOptions,
  GetPostOptions,
  CacheOptions,
  PostsListProps,
  LatestPostsProps,
  PostDetailProps,
} from '@content-streamline/nextjs';

function MyComponent({ post }: { post: PostDetailResponse }) {
  // Full type safety
}

CSS Classes Reference

LatestPosts

  • .content-streamline-latest-posts - Container
  • .content-streamline-latest-card - Individual card
  • .content-streamline-latest-card--featured - First (featured) card
  • .content-streamline-latest-image - Image container
  • .content-streamline-latest-content - Content area
  • .content-streamline-latest-date - Date element
  • .content-streamline-latest-title - Title
  • .content-streamline-latest-excerpt - Excerpt text
  • .content-streamline-latest-link - "Read more" link

PostsList

  • .content-streamline-list - Container
  • .content-streamline-list-item - Individual item
  • .content-streamline-list-image - Image thumbnail
  • .content-streamline-list-content - Content area
  • .content-streamline-list-date - Date element
  • .content-streamline-list-title - Title
  • .content-streamline-list-excerpt - Excerpt text

PostDetail

  • .content-streamline-post-detail - Container
  • .content-streamline-back-link - Back navigation link
  • .content-streamline-post-header - Header section
  • .content-streamline-post-title - Title
  • .content-streamline-post-meta - Meta information
  • .content-streamline-post-date - Date
  • .content-streamline-post-type - Type badge
  • .content-streamline-post-featured-image - Featured image
  • .content-streamline-post-content - Content area
  • .content-streamline-post-gallery - Image gallery

Common

  • .content-streamline-empty - Empty state message
  • .content-streamline-post-not-found - Not found message

License

MIT