Archiva Next.js SDK - Server Actions and Timeline Component
npm install @archiva/archiva-nextjsbash
npm install @archiva/archiva-nextjs
or
pnpm add @archiva/archiva-nextjs
or
yarn add @archiva/archiva-nextjs
`
Quick Start
$3
Set the ARCHIVA_SECRET_KEY environment variable in your .env.local file:
`env
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.
$3
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
`tsx
import { GET } from '@archiva/archiva-nextjs/server';
// Export the GET handler - that's it!
export { GET };
`
Or with custom configuration:
`tsx
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 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
`tsx
import { ArchivaProvider } from '@archiva/archiva-nextjs/react';
export default function RootLayout({
children,
}: {
children: React.ReactNode;
}) {
return (
{children}
);
}
`
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:
`tsx
import { ArchivaProvider } from '@archiva/archiva-nextjs/react';
export default function RootLayout({
children,
}: {
children: React.ReactNode;
}) {
return (
apiBaseUrl="https://api.archiva.app"
tokenEndpoint="/api/archiva/frontend-token"
projectId="proj_123" // Optional: for project-scoped tokens
>
{children}
);
}
`
$3
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
`tsx
'use client';
import { Timeline } from '@archiva/archiva-nextjs/react/client';
export default function InvoicePage({ params }: { params: { id: string } }) {
return (
Invoice {params.id}
entityId={params.id}
entityType="invoice"
initialLimit={25}
/>
);
}
`
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
$3
`
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
$3
Access the Archiva context directly in your components:
`tsx
'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 (
{events.map((event) => (
{event.action}
))}
);
}
`
$3
Force a token refresh if needed:
`tsx
'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 ;
}
`
$3
If you want tokens scoped to a specific project, pass projectId to the provider:
`tsx
{children}
`
Or pass it as a query parameter to your token endpoint:
`tsx
// GET /api/archiva/frontend-token?projectId=proj_123
`
API Reference
$3
`ts
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
};
`
$3
`ts
type TimelineProps = {
entityId?: string;
actorId?: string;
entityType?: string;
initialLimit?: number; // Default: 25
className?: string;
emptyMessage?: string; // Default: 'No events yet.'
};
`
$3
`ts
type ArchivaContextValue = {
apiBaseUrl: string;
getToken: () => Promise;
forceRefreshToken: () => Promise;
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:
$3
`tsx
// ✅ Correct - Server-safe import
import { ArchivaProvider } from '@archiva/archiva-nextjs/react';
`
$3
`tsx
'use client';
// ✅ Correct - Client-only imports
import { Timeline, useArchiva } from '@archiva/archiva-nextjs/react/client';
`
$3
`tsx
// ✅ Correct - Server utilities
import { GET } from '@archiva/archiva-nextjs/server';
`
$3
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:
`tsx
// ⚠️ 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
$3
This error occurs when the environment variable cannot be found. Follow these steps:
1. Check your .env.local file (preferred) or .env file:
`env
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:
`ts
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)
$3
Wrap your component tree with . The provider must be a server component in your layout.
$3
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
$3
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