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

@archiva/archiva-nextjs

v0.3.2

Published

Archiva Next.js SDK - Server Actions and Timeline Component

Readme

@archiva/archiva-nextjs

Next.js SDK for Archiva - Frontend Token Provider and Timeline Component

Installation

npm install @archiva/archiva-nextjs
# or
pnpm add @archiva/archiva-nextjs
# or
yarn add @archiva/archiva-nextjs

Quick Start

1. Set Environment Variable

Set the ARCHIVA_SECRET_KEY environment variable in your .env.local file:

ARCHIVA_SECRET_KEY=pk_test_xxxxx

Important: This should be a valid Archiva API key. The SDK uses this to mint short-lived frontend tokens securely on the server.

2. Create Token Endpoint Route

Create a Next.js API route to handle frontend token requests. This route will be called by the SDK to fetch short-lived tokens.

File: app/api/archiva/frontend-token/route.ts

import { GET } from '@archiva/archiva-nextjs/server';

// Export the GET handler - that's it!
export { GET };

Or with custom configuration:

import { createFrontendTokenRoute } from '@archiva/archiva-nextjs/server';

export const GET = createFrontendTokenRoute({
  apiBaseUrl: process.env.ARCHIVA_API_URL, // Optional: defaults to https://api.archiva.app
});

3. Wrap Your App with ArchivaProvider

Wrap your application layout with the ArchivaProvider component. This is a server component that validates your ARCHIVA_SECRET_KEY and provides the token management context to client components.

File: app/layout.tsx

import { ArchivaProvider } from '@archiva/archiva-nextjs/react';

export default function RootLayout({
  children,
}: {
  children: React.ReactNode;
}) {
  return (
    <html lang="en">
      <body>
        <ArchivaProvider>
          {children}
        </ArchivaProvider>
      </body>
    </html>
  );
}

Important: Always import ArchivaProvider from @archiva/archiva-nextjs/react (not from the root package). This ensures the import is server-safe and won't trigger RSC errors.

With custom configuration:

import { ArchivaProvider } from '@archiva/archiva-nextjs/react';

export default function RootLayout({
  children,
}: {
  children: React.ReactNode;
}) {
  return (
    <html lang="en">
      <body>
        <ArchivaProvider
          apiBaseUrl="https://api.archiva.app"
          tokenEndpoint="/api/archiva/frontend-token"
          projectId="proj_123" // Optional: for project-scoped tokens
        >
          {children}
        </ArchivaProvider>
      </body>
    </html>
  );
}

4. Use the Timeline Component

Now you can use the Timeline component in any client component. It will automatically:

  • Fetch short-lived frontend tokens
  • Call the Archiva API directly (no proxy routes needed)
  • Handle token refresh automatically
  • Retry on 401/403 errors

File: app/invoice/[id]/page.tsx

'use client';

import { Timeline } from '@archiva/archiva-nextjs/react/client';

export default function InvoicePage({ params }: { params: { id: string } }) {
  return (
    <div>
      <h1>Invoice {params.id}</h1>
      <Timeline
        entityId={params.id}
        entityType="invoice"
        initialLimit={25}
      />
    </div>
  );
}

Note: Client components like Timeline and useArchiva must be imported from @archiva/archiva-nextjs/react/client to ensure proper client/server code splitting.

How It Works

The SDK implements a Clerk-style provider pattern with short-lived frontend tokens:

  1. Server Component (ArchivaProvider): Validates ARCHIVA_SECRET_KEY exists (never exposes it to the client)
  2. Client Component (ArchivaProviderClient): Manages token lifecycle:
    • Fetches tokens from your /api/archiva/frontend-token route
    • Caches tokens in memory
    • Auto-refreshes 30 seconds before expiry
    • Handles 401/403 errors with automatic retry
  3. Timeline Component: Uses tokens to call Archiva API directly

Token Flow

Client Component → useArchiva().getToken()
  ↓
ArchivaProviderClient checks cache
  ↓
If expired/absent → GET /api/archiva/frontend-token
  ↓
Your route → POST /v1/frontend-tokens (Archiva API)
  ↓
Server mints JWT (90s expiry)
  ↓
Token returned → Cached → Used for API calls

Advanced Usage

Using the useArchiva Hook

Access the Archiva context directly in your components:

'use client';

import { useArchiva } from '@archiva/archiva-nextjs/react/client';

export function CustomTimeline({ entityId }: { entityId: string }) {
  const { apiBaseUrl, getToken, forceRefreshToken } = useArchiva();
  const [events, setEvents] = React.useState([]);

  React.useEffect(() => {
    async function loadEvents() {
      const token = await getToken();
      
      const response = await fetch(`${apiBaseUrl}/api/events?entityId=${entityId}`, {
        headers: {
          Authorization: `Bearer ${token}`,
        },
      });

      if (!response.ok) {
        // SDK automatically handles 401/403 with retry
        throw new Error('Failed to load events');
      }

      const data = await response.json();
      setEvents(data.items);
    }

    loadEvents();
  }, [entityId, apiBaseUrl, getToken]);

  return (
    <div>
      {events.map((event) => (
        <div key={event.id}>{event.action}</div>
      ))}
    </div>
  );
}

Manual Token Refresh

Force a token refresh if needed:

'use client';

import { useArchiva } from '@archiva/archiva-nextjs/react/client';

export function RefreshButton() {
  const { forceRefreshToken } = useArchiva();

  const handleRefresh = async () => {
    try {
      const newToken = await forceRefreshToken();
      console.log('Token refreshed:', newToken);
    } catch (error) {
      console.error('Failed to refresh token:', error);
    }
  };

  return <button onClick={handleRefresh}>Refresh Token</button>;
}

Project-Scoped Tokens

If you want tokens scoped to a specific project, pass projectId to the provider:

<ArchivaProvider projectId="proj_123">
  {children}
</ArchivaProvider>

Or pass it as a query parameter to your token endpoint:

// GET /api/archiva/frontend-token?projectId=proj_123

API Reference

ArchivaProvider Props

type ArchivaProviderProps = {
  children: ReactNode;
  apiBaseUrl?: string; // Default: 'https://api.archiva.app'
  tokenEndpoint?: string; // Default: '/api/archiva/frontend-token'
  projectId?: string; // Optional: for project-scoped tokens
};

Timeline Props

type TimelineProps = {
  entityId?: string;
  actorId?: string;
  entityType?: string;
  initialLimit?: number; // Default: 25
  className?: string;
  emptyMessage?: string; // Default: 'No events yet.'
};

useArchiva Hook

type ArchivaContextValue = {
  apiBaseUrl: string;
  getToken: () => Promise<string>;
  forceRefreshToken: () => Promise<string>;
  projectId?: string;
};

Security

  • No secrets in client bundles: ARCHIVA_SECRET_KEY is only used on the server
  • Short-lived tokens: Tokens expire in 90 seconds
  • Automatic refresh: Tokens refresh 30 seconds before expiry
  • Scope enforcement: Tokens require timeline:read scope
  • Project scoping: Tokens can be scoped to specific projects

Import Paths

The SDK provides explicit entrypoints to ensure proper server/client code splitting:

Server Components (e.g., app/layout.tsx)

// ✅ Correct - Server-safe import
import { ArchivaProvider } from '@archiva/archiva-nextjs/react';

Client Components

'use client';

// ✅ Correct - Client-only imports
import { Timeline, useArchiva } from '@archiva/archiva-nextjs/react/client';

Server Routes (e.g., app/api/archiva/frontend-token/route.ts)

// ✅ Correct - Server utilities
import { GET } from '@archiva/archiva-nextjs/server';

Why Separate Entrypoints?

Next.js App Router requires strict separation between server and client code. The SDK splits exports to prevent RSC errors:

  • @archiva/archiva-nextjs/react - Server-safe exports (ArchivaProvider only)
  • @archiva/archiva-nextjs/react/client - Client-only exports (Timeline, useArchiva)
  • @archiva/archiva-nextjs/server - Server utilities (route handlers)

Important: Do NOT import client components from the root package or from /react in Server Components, as this will trigger RSC errors.

Backward Compatibility

Legacy exports are still available from the root package for backward compatibility, but they are deprecated:

// ⚠️ Legacy (deprecated - may cause RSC errors in Server Components)
import { ArchivaProvider, Timeline } from '@archiva/archiva-nextjs';

For new projects, always use the explicit entrypoints shown above.

Troubleshooting

"ARCHIVA_SECRET_KEY not configured" or "ARCHIVA_SECRET_KEY environment variable is required"

This error occurs when the environment variable cannot be found. Follow these steps:

  1. Check your .env.local file (preferred) or .env file:

    ARCHIVA_SECRET_KEY=pk_test_xxxxx
    • Make sure there are no spaces around the = sign
    • Make sure there are no quotes around the value (unless the value itself contains quotes)
    • The file should be in the root of your Next.js project (same directory as package.json)
  2. Restart your Next.js dev server:

    • Stop the server (Ctrl+C)
    • Start it again (npm run dev or pnpm dev)
    • Next.js only loads environment variables on startup
  3. Verify the variable is loaded:

    • Add a temporary log in your route handler:
    console.log('ARCHIVA_SECRET_KEY:', process.env.ARCHIVA_SECRET_KEY ? 'Set' : 'Not set');
    • Check the server console (not browser console) for the output
  4. Check file location:

    • .env.local should be in the project root (same level as next.config.js)
    • Not in src/, app/, or any subdirectory
  5. For production deployments:

    • Set the environment variable in your hosting platform (Vercel, Railway, etc.)
    • Don't commit .env.local to git (it should be in .gitignore)

"useArchivaContext must be used within an ArchivaProvider"

Wrap your component tree with <ArchivaProvider>. The provider must be a server component in your layout.

Token endpoint returns 401

Verify that:

  1. ARCHIVA_SECRET_KEY is set correctly
  2. The API key is valid and active
  3. Your token endpoint route is correctly set up

Timeline shows "Error: Failed to fetch frontend token"

Check that:

  1. Your /api/archiva/frontend-token route exists
  2. The route handler is exported correctly
  3. ARCHIVA_SECRET_KEY is accessible to the route handler

License

MIT