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

@fopost/next

v0.1.1

Published

Official Next.js integration for the FoPost API. Server-only client, webhook route handlers, and Server Action helpers for scheduling and publishing to +30 social platforms.

Readme

@fopost/next

npm license ci release

Official Next.js integration for the FoPost API. Schedule and publish to +30 social platforms from your app.

This is a thin wrapper around @fopost/sdk. Every request, retry, model and error class lives there; this package only wires FoPost into Next.js idioms — a server-only client, a webhook Route Handler, Server Action helpers, and cache tags.

npm install @fopost/next

Requires Node 20+, Next 14 or 15, and React 18.2+. Ships ESM and CommonJS builds with TypeScript types.

0.x release. The public API is still settling and minor versions may contain breaking changes. Pin an exact version if that matters to you.

The server-only guarantee

A FoPost API key must never reach the browser, and this package enforces that rather than documenting it.

  • The module that constructs the client imports server-only. Bundling @fopost/next into a Client Component is a build error, not a runtime surprise.
  • The key is read from FOPOST_API_KEY. There is no NEXT_PUBLIC_* path to it, and getFoPostClient() will not fall back to one.
  • Call it from a Server Component, a Route Handler, or a Server Action. Pass data to Client Components, never the client.

If you see This module cannot be imported from a Client Component module, a 'use client' file is importing @fopost/next. Move the call into a Server Action and import that instead.

Environment

| Variable | Required | What it does | | ----------------------- | -------- | ---------------------------------------------------------- | | FOPOST_API_KEY | yes | Your API key, sent as X-API-Key. Server-side only | | FOPOST_BASE_URL | no | Override the API base URL | | FOPOST_WEBHOOK_SECRET | webhooks | Signing secret of the webhook you created in the dashboard |

Get a key at fopost.com/dashboard/api-keys.

Server Component

// app/posts/page.tsx
import { getFoPostClient } from '@fopost/next';

export default async function PostsPage() {
  const fopost = getFoPostClient();
  const posts = await fopost.posts.list({ workspaceId: process.env.FOPOST_WORKSPACE_ID! });

  return (
    <ul>
      {posts.map((post) => (
        <li key={post.id}>
          {post.title ?? post.id} — {post.status}
        </li>
      ))}
    </ul>
  );
}

getFoPostClient() is memoized per resolved credentials, so calling it in every component and action is free.

Server Actions

Action return values are serialized across the RSC boundary, so an SDK error instance cannot be thrown through one. Every helper returns a discriminated result with a plain-object error instead.

// app/posts/actions.ts
'use server';

import { createPostAction, publishPostAction, foPostTags } from '@fopost/next';

const revalidate = { tags: [foPostTags.posts(WORKSPACE_ID)], paths: ['/posts'] };

const createPost = createPostAction({ revalidate });
const publishPost = publishPostAction({ revalidate });

export async function composeAndPublish(formData: FormData) {
  const created = await createPost({
    workspaceId: WORKSPACE_ID,
    content: [{ text: String(formData.get('text')) }],
    accounts: formData.getAll('accounts').map(String),
  });
  if (!created.ok) return created; // { ok: false, error: { message, status, code } }

  return publishPost(created.data.id);
}
// app/posts/compose-form.tsx
'use client';
import { composeAndPublish } from './actions';

// The action is imported; the client and the key are not.
<form action={composeAndPublish}>...</form>;

| Helper | Wraps | | ------------------------ | ------------------------ | | createPostAction() | posts.create | | updatePostAction() | posts.update | | publishPostAction() | posts.publish | | deletePostAction() | posts.delete | | createFoPostAction(fn) | anything else in the SDK |

Publishing is create then publish, and publish resolves when delivery is queued, not when the post is live. The post.published webhook tells you it landed.

Webhooks

The API signs the exact bytes it sends:

X-FoPost-Signature: sha256=<hex HMAC-SHA256 of the raw body, keyed by your webhook secret>
X-FoPost-Event:     post.published
X-FoPost-Delivery:  <delivery id>

The handler reads the raw body before parsing it, verifies with a constant-time compare, and answers 401 on a mismatch. Re-serializing the parsed JSON would change the bytes and break verification, which is exactly the mistake this helper removes.

// app/api/fopost/webhook/route.ts
import { createFoPostWebhookHandler } from '@fopost/next';

export const runtime = 'nodejs'; // signature verification uses node:crypto

export const POST = createFoPostWebhookHandler({
  // secret defaults to process.env.FOPOST_WEBHOOK_SECRET
  on: {
    'post.published': async ({ payload, deliveryId }) => {
      await recordPublished(payload.data, deliveryId);
    },
    '*': async ({ event }) => log(event), // runs after the specific handler
  },
  revalidate: true, // drop the cache tags this event affects
  onError: (error) => report(error),
});

Events: post.published, post.failed, post.partially_failed, delivery.published, delivery.failed, delivery.delayed, account.health_changed.

Responses: 200 {"received":true} · 401 invalid_signature · 400 invalid_payload / unknown_event · 500 webhook_not_configured / handler_failed.

verifyFoPostSignature({ rawBody, signature, secret }) is exported if you want to verify by hand.

Pages Router

The Pages Router parses the body before your handler runs, which destroys the signed bytes. Re-export the config so Next hands over the raw stream:

// pages/api/fopost/webhook.ts
import { createFoPostWebhookApiRoute, foPostWebhookApiConfig } from '@fopost/next';

export const config = foPostWebhookApiConfig; // { api: { bodyParser: false } }

export default createFoPostWebhookApiRoute({
  on: { 'post.published': async ({ payload }) => recordPublished(payload.data) },
});

Caching and revalidation

foPostTags gives you stable tags to hang FoPost reads on, and the webhook drops them when the API says something changed.

import { foPostTags, revalidateFoPost } from '@fopost/next';

// Tag a read in a Server Component
await fetch(`${process.env.FOPOST_BASE_URL}/v1/posts?workspace_id=${id}`, {
  headers: { 'X-API-Key': process.env.FOPOST_API_KEY! },
  next: { tags: [foPostTags.posts(id)], revalidate: 60 },
});

// Drop it by hand
await revalidateFoPost({ tags: [foPostTags.posts(id)], paths: ['/posts'] });

| Tag | Covers | | ------------------------------------------------------ | ----------------------- | | foPostTags.all() | everything FoPost | | foPostTags.posts(workspaceId?) | a workspace's post list | | foPostTags.post(postId) | one post | | foPostTags.accounts(workspaceId?) | connected accounts | | foPostTags.account(accountId) | one account | | foPostTags.workspaces() / foPostTags.workspace(id) | workspaces | | foPostTags.analytics(workspaceId?) | analytics reads |

revalidate: true on the webhook handler derives the tags from the event; pass an object or a function for full control.

The rest of the API

Resources, DTOs and FoPostError are re-exported from @fopost/sdk for convenience:

import { getFoPostClient, FoPostError } from '@fopost/next';

const fopost = getFoPostClient();
await fopost.accounts.list({ workspaceId });
await fopost.ai.generateCaption({ currentCaption: 'shipping today' });

See the @fopost/sdk README for the full surface — posts, accounts, workspaces, labels, ai — plus retries, error handling and the transport contract.

Examples

examples/app-router is a runnable App Router slice: a Server Component read, a Server Action that creates and publishes, the client form that calls it, and the webhook route.

Contributing

Issues and pull requests are welcome at fopost/fopost-next.

npm install
npm run typecheck   # tsc --noEmit
npm test            # vitest, fully offline
npm run build       # tsup -> dist/

Docs live at fopost.com/docs. For anything else, use the contact form.

License

MIT © Porter Bridge, LLC