@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-nextjsQuick Start
1. Set Environment Variable
Set the ARCHIVA_SECRET_KEY environment variable in your .env.local file:
ARCHIVA_SECRET_KEY=pk_test_xxxxxImportant: 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:
- Server Component (
ArchivaProvider): ValidatesARCHIVA_SECRET_KEYexists (never exposes it to the client) - Client Component (
ArchivaProviderClient): Manages token lifecycle:- Fetches tokens from your
/api/archiva/frontend-tokenroute - Caches tokens in memory
- Auto-refreshes 30 seconds before expiry
- Handles 401/403 errors with automatic retry
- Fetches tokens from your
- 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 callsAdvanced 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_123API 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_KEYis 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:readscope - 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:
Check your
.env.localfile (preferred) or.envfile: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)
- Make sure there are no spaces around the
Restart your Next.js dev server:
- Stop the server (Ctrl+C)
- Start it again (
npm run devorpnpm dev) - Next.js only loads environment variables on startup
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
Check file location:
.env.localshould be in the project root (same level asnext.config.js)- Not in
src/,app/, or any subdirectory
For production deployments:
- Set the environment variable in your hosting platform (Vercel, Railway, etc.)
- Don't commit
.env.localto 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:
ARCHIVA_SECRET_KEYis set correctly- The API key is valid and active
- Your token endpoint route is correctly set up
Timeline shows "Error: Failed to fetch frontend token"
Check that:
- Your
/api/archiva/frontend-tokenroute exists - The route handler is exported correctly
ARCHIVA_SECRET_KEYis accessible to the route handler
License
MIT
