@rakun-kit/next
v1.3.11
Published
Next.js adapter for Rakun APIs, manager UI, and media endpoints.
Downloads
1,892
Readme
@rakun-kit/next
Next.js App Router adapter for Rakun. It provides a catch-all route handler, optional tRPC mounting, local media handlers, and a manager page component.
API Route Handler
Create a catch-all App Router route and export the methods returned by
rakunNext:
// app/api/rakun/[...slug]/route.ts
import { rakunNext } from "@rakun-kit/next";
export const { GET, POST, PUT } = rakunNext({
bootstrap: {
literals,
contentTypes: [Page, Post],
mongo: {
MONGO_URI: process.env.MONGO_URI!,
},
},
});By default, the handler:
- calls
ensureRakunBootstrap(bootstrap)whenbootstrapis provided. - calls
ensureRakunInitialized()before each request. - serves
GET /api/rakun/healthunlesshealthPathisfalse. - serves Rakun operation routes through
rakunNextCrud(). - serves media upload routes when media is configured.
Options:
type RakunNextOptions = {
bootstrap?: RakunBootstrapOptions;
healthPath?: string | false;
integrations?: RakunNextIntegration[];
};Use integrations for extra handlers. Each integration receives the Fetch
Request, Next route context, and normalized path segments. The first
integration that returns a Response wins.
tRPC
Mount a tRPC router in the same catch-all route with @rakun-kit/next/trpc:
// app/api/rakun/[...slug]/route.ts
import { rakunNext, rakunNextCrud } from "@rakun-kit/next";
import { rakunNextTrpc } from "@rakun-kit/next/trpc";
import { appRouter } from "@/server/trpc";
export const { GET, POST, PUT } = rakunNext({
bootstrap,
integrations: [
rakunNextCrud(),
rakunNextTrpc({
path: "trpc",
router: appRouter,
}),
],
});The tRPC integration creates a Rakun request context from Fetch API headers, cookies, and response headers.
Web Pages
Fetch Rakun page data from a Next Server Component with @rakun-kit/next/web:
// app/[[...slug]]/page.tsx
import {
createRakunPageMetadata,
getRakunPage,
getRakunPathFromParams,
RakunPageRenderer,
type RakunNextPageParams,
type RakunNextPageSearchParams,
} from "@rakun-kit/next/web";
type Props = {
params: Promise<RakunNextPageParams>;
searchParams: Promise<RakunNextPageSearchParams>;
};
export async function generateMetadata({ params, searchParams }: Props) {
const page = await getRakunPage({
path: getRakunPathFromParams({ params: await params }),
search: await searchParams,
apiBaseUrl: "/api/rakun",
});
return createRakunPageMetadata(page);
}
export default async function Page({ params, searchParams }: Props) {
const page = await getRakunPage({
path: getRakunPathFromParams({ params: await params }),
search: await searchParams,
apiBaseUrl: "/api/rakun",
});
return (
<RakunPageRenderer
page={page}
loadModule={(name) => import(`@/modules/${name}`)}
/>
);
}RakunPageRenderer is a server component renderer. Modules are server modules
by default, so they can fetch data and render without client JavaScript. If a
module needs hooks or browser events, add "use client" at the top of that
module file; Next.js will make only that module a client component.
When a Rakun page response includes a redirect, the renderer calls the matching
Next.js redirect helper before rendering modules.
Module files should export either default or component:
// modules/Hero.tsx
export default function Hero({ title }: { title: string }) {
return <section>{title}</section>;
}getRakunPage forwards request headers by default, including cookies, so
redirects, preview state, locale, and auth-aware logic can use request context.
It defaults to cache: "no-store". Pass fetchOptions for ISR/cache control:
await getRakunPage({
path: "/",
fetchOptions: {
next: { revalidate: 86400, tags: ["rakun-page:/"] },
},
});Web API Client
Use @rakun-kit/next/web/client from client components that need to call
custom Rakun operations. Keep the operation map in a separate server file, pass
it to bootstrap, and import its type in the client:
// server/api-operations.ts
import { defineOperation } from "@rakun-kit/next";
import { z } from "zod";
export const apiOperations = {
"demo.helloWorld": defineOperation<
{ text: string },
{ message: string },
"query",
"get",
"public"
>({
access: "public",
kind: "query",
method: "get",
input: z.object({
text: z.string().default("world"),
}),
output: z.object({
message: z.string(),
}),
resolve: ({ input }) => ({
message: `Hello ${input.text}`,
}),
}),
};// app/api/rakun/[...slug]/route.ts
import { rakunNext } from "@rakun-kit/next";
import { apiOperations } from "@/server/api-operations";
export const { GET, POST, PUT } = rakunNext({
bootstrap: {
// ...
apiOperations,
},
});// modules/HelloWorld.tsx
"use client";
import {
createRakunApiClient,
type GetClient,
} from "@rakun-kit/next/web/client";
import type { apiOperations } from "@/server/api-operations";
type ApiClient = GetClient<typeof apiOperations>;
const apiClient: ApiClient = createRakunApiClient<typeof apiOperations>({
baseUrl: "/api/rakun",
});
const result = await apiClient.query("demo.helloWorld", {
text: "Rakun",
});query only accepts operations declared with kind: "query". mutation only
accepts operations declared with kind: "mutation". Input and output types are
derived from each operation's Zod schemas.
Manager Page
Render the manager from a Next App Router page with @rakun-kit/next/manager:
// app/backend/[[...slug]]/page.tsx
import {
RakunManagerPage,
type RakunManagerPageProps,
} from "@rakun-kit/next/manager";
export default function Page(props: RakunManagerPageProps) {
return (
<RakunManagerPage
{...props}
apiBaseUrl="/api/rakun"
basePath="/backend"
/>
);
}RakunManagerPage expects Next's Promise-based params and searchParams
props.
Options:
apiBaseUrl: base URL used by the manager HTTP client. Defaults to/api.managerClient: custom manager client. If omitted, a HTTP client is created.basePath: manager mount path. Defaults to/backend.paramKey: route param key used to read path segments. Defaults toslug.loadingFallback,unauthenticatedFallback: optional React fallbacks.
Local Media
Use createLocalMediaServiceConfig from @rakun-kit/next/media in bootstrap media
config:
// app/api/rakun/[...slug]/route.ts
import path from "node:path";
import { rakunNext } from "@rakun-kit/next";
import { createLocalMediaServiceConfig } from "@rakun-kit/next/media";
export const { GET, POST, PUT } = rakunNext({
bootstrap: {
// ...
media: createLocalMediaServiceConfig({
rootDir: path.join(process.cwd(), ".rakun/media"),
baseUrl: "/api/rakun",
publicBaseUrl: "/api/rakun",
tokenSecret: process.env.RAKUN_MEDIA_TOKEN_SECRET!,
defaultAccess: "private",
}),
},
});When this config is detected, rakunNext serves:
PUT /api/rakun/media/local/upload/:tokenGET /api/rakun/media/local/private/:tokenGET /api/rakun/media/public/*
Exports
@rakun-kit/next:rakunNext,rakunNextCrud, local media helpers, and shared route utilities.@rakun-kit/next/trpc:rakunNextTrpc.@rakun-kit/next/media:LocalAdapter, local media config, and local HTTP handlers.@rakun-kit/next/manager:RakunManagerPageand manager page types.@rakun-kit/next/web:getRakunPage,RakunPageRenderer, and page path helpers.@rakun-kit/next/web/client: Rakun React renderers and typed API client helpers for client components.
Build
bun run build --workspace @rakun-kit/next