@rakun-kit/bun
v0.1.11
Published
Bun framework adapter for Rakun APIs, manager, filesystem modules, SSR, and static routes.
Readme
@rakun-kit/bun
Bun 1.4 framework adapter for Rakun. It runs Rakun API, manager, web rendering,
static routes, dynamic routes, and development reloads from one Bun.serve()
process.
Install
bun add @rakun-kit/bun @rakun-kit/core @rakun-kit/manager-react @rakun-kit/react react react-domBun uses its native password implementation, so this adapter does not require
the bcrypt package.
Add scripts to the application:
{
"scripts": {
"dev": "rakun-bun dev",
"build": "rakun-bun build",
"start": "rakun-bun start"
}
}Create a typed rakun.config.ts. bootstrap accepts the same
RakunBootstrapOptions used by the other Rakun adapters; the remaining fields
configure the Bun framework:
import type { RakunBunConfig } from '@rakun-kit/bun'
import { createRakunBootstrap } from './src/rakun/bootstrap'
const bunConfig: RakunBunConfig = {
bootstrap: createRakunBootstrap,
modulesDir: './src/modules',
revalidation: {
token: process.env.RAKUN_REVALIDATE_TOKEN!,
},
}
export default bunConfigThe default paths are /api, /manager, /_rakun/rsc, /_rakun/revalidate,
and /assets. revalidation configures core to call the same Bun process after
Rakun resolves affected content to paths.
Files in public/ at the application root (next to src/) are served from the
same URL path, before web page rendering. Production builds copy that directory
to dist/public, while development serves it directly from the source folder.
The manager preview is enabled for the same Bun origin by default. Set
manager: { preview: false } to disable it, or provide webBaseUrl and
tokenParam when the web application is hosted elsewhere or uses a custom
preview query parameter. Preview requests with that token are resolved through
Rakun's web.previewPage operation.
Production route and gzip caches are bounded by count, bytes, and idle time. The defaults retain at most 128 rendered routes and 32 MiB in each cache, purge entries after five idle minutes, and keep the newest two complete disk generations per static route. Override them when needed:
const bunConfig: RakunBunConfig = {
cache: {
routeMaxEntries: 64,
routeMaxBytes: 16 * 1024 * 1024,
routeIdleTimeoutMs: 60_000,
routeMaxGenerations: 2,
assetMaxBytes: 16 * 1024 * 1024,
assetIdleTimeoutMs: 60_000,
},
}Expired routes are reloaded from disk without regeneration. Use 0 for an idle
timeout to disable time-based eviction, or 0 for a byte/entry limit to disable
that memory cache.
Filesystem modules
Both forms are discovered without an index registry:
src/modules/Hero.tsx
src/modules/Gallery/index.tsxThe resulting names are Hero and Gallery. Duplicate names fail the build.
Modules are server-rendered by default and their code is absent from browser
bundles. A top-level 'use client' directive creates a self-contained browser
bundle and a hydrated island. Rakun also follows static imports, so a server
module is promoted to a client boundary when it imports a client component from
another file or Image (and other client exports) from @rakun-kit/react:
'use client'
import { useState } from 'react'
export default function Counter({ initial = 0 }) {
const [value, setValue] = useState(initial)
return <button onClick={() => setValue(value + 1)}>{value}</button>
}An unresolved route uses the framework's empty NotFound fallback and returns
HTTP 404. Add src/modules/NotFound.tsx to render an application-specific 404
module; Bun keeps the response status and marker automatically.
Rakun page content stays separate from this code. A content save regenerates only the HTML and path-scoped render payload; source changes rebuild code and hashed assets.
Rendering and navigation
web.staticPaths is the source of truth for build-time routes. Each static path
gets HTML plus a flight.rsc render payload. Other paths resolve through
web.page at request time. Client navigation requests /_rakun/rsc/*, swaps
the rendered tree, and imports only client bundles referenced by the
destination page. Web module bundles, navigation, and the manager are built as
independent graphs, so a public page never downloads manager code or incidental
shared chunks. The manager keeps its own lazy-loaded graph under
/assets/manager/; route chunks produced by React.lazy load only after that
manager route is visited. Production builds consolidate the manager shell into
one initial script, emit one lazy root bundle per built-in manager screen, and
retain only genuinely shared supporting chunks. Lucide's runtime registry is
also reduced to the menu and module-picker icons declared by the bootstrapped
content types instead of emitting the complete icon catalog.
The manager receives a Bun-provided linkComponent; Bun owns manager click
routing and prefetches built-in route chunks on hover, focus, or touch.
Internal links prefetch their flight and destination client modules on hover, focus, or touch, so the subsequent navigation can reuse the warmed response. API and manager links keep their normal full-document navigation behavior.
Use the exported Link component for web links when prefetch needs to be
disabled for a specific destination:
import { Link } from '@rakun-kit/bun'
<Link href="/about">About</Link>
<Link href="/large-report" prefetch={false}>Large report</Link>Client modules can read the current URL pathname with usePathname(). The hook
returns the server-rendered pathname on first render and updates after Rakun
client navigation, including browser back and forward navigation:
'use client'
import { usePathname } from '@rakun-kit/bun'
export default function NavigationStatus() {
const pathname = usePathname()
return <span>{pathname}</span>
}Add src/document.tsx to define the application shell. It is a server component
and follows the same children layout shape as a Next.js root layout:
import type { RakunBunDocumentProps } from '@rakun-kit/bun'
import './globals.css'
export default function Document({ children, page }: RakunBunDocumentProps) {
return (
<html lang={page.language?.code ?? 'en'}>
<head>
<link rel="icon" href="/favicon.svg" />
</head>
<body>
<div className="site">{children}</div>
</body>
</html>
)
}The framework bundles global CSS imported by the document and injects page SEO,
styles, navigation scripts, and the Rakun root inside the rendered document. The
file must export a default server component. Configure PostCSS plugins through
css.plugins; Rakun runs them for CSS from the application source tree in both
development and production. For Tailwind CSS v4:
bun add -d tailwindcss @tailwindcss/postcssimport tailwindcss from '@tailwindcss/postcss'
const bunConfig: RakunBunConfig = {
css: {
plugins: [tailwindcss()],
},
}Then import @import 'tailwindcss'; from the stylesheet directly. No generated
CSS file or separate watcher is required.
Path invalidation
The public primitive is invalidatePath(path). Static regeneration renders a
new generation separately, writes complete HTML and render payload files, then
swaps the in-memory route pointer. Failed rendering leaves the previous version
available.
const app = createRakunBun(config)
await app.invalidatePath('/about')The authenticated HTTP endpoint accepts the existing core revalidation shape:
POST /_rakun/revalidate
Authorization: Bearer <token>
Content-Type: application/json
{"path":"/about"}invalidateTag is intentionally not implemented. Rakun remains responsible for
content relationships and calculating affected paths.
Static HTML and flight responses require browser revalidation, so a regenerated
path cannot remain hidden behind a stale browser cache. Requests carrying the
manager preview token bypass the static route cache entirely and are served
with Cache-Control: no-store.
Development and production
rakun-bun dev watches src and an external module directory when configured.
It rebuilds the document and server graph for server-only changes, rebuilds only
the affected client entries when a client module changes, invalidates static
routes for lazy regeneration, and replaces the rendered tree over the
development WebSocket. A failed rebuild leaves the current application active;
a browser-side update failure falls back to a page reload.
The first development build stores the browser, navigation, and manager build
metadata and assets in .rakun/cache. Later starts use file metadata as a fast
path and verify changed candidates against the stored hashes before reusing the
client, navigation, and manager output. The cache is independent from dist, so
a production build does not make the next development start cold. Missing cached
outputs or a changed dependency cause a normal rebuild. Incremental client
rebuilds refresh the persistent cache for the following process. The server graph
has its own validated cache and is imported into each new process; changing any
of its source inputs rebuilds it. The cache is disposable and .rakun/ should
remain ignored by source control.
Development does not load or prerender web.staticPaths. Every web and flight
request is rendered dynamically with Cache-Control: no-store, even when core
marks the page as static. Route-cache persistence and static generation are only
used by production builds and production servers. Rakun initialization runs in
parallel with development code compilation to reduce cold-start latency.
Normal web pages automatically include the Rakun development toolbar. It shows route and document metadata, links to the configured manager, lists rendered layout/template/content modules, highlights their wrapper-free DOM roots, and inspects the props received by each module. It follows client-side Bun navigation and reload updates. Manager preview pages keep their dedicated inspection flow and do not mount this toolbar. Production output contains neither the toolbar bundle nor its instrumentation markers.
rakun-bun build displays the current phase and elapsed time while it loads
configuration, bundles code, renders static routes, creates the production
server, and analyzes the output. Interactive terminals use a spinner; redirected
output and CI receive stable log lines without terminal control codes.
The command writes:
dist/
server.js
assets/
routes/
manifests/
build.json
client.json
modules.json
routes.jsonThe build report includes elapsed time and lists prerendered routes with their
HTML, flight, raw client asset, and gzip transfer sizes, followed by each client
bundle with its raw and gzip sizes, runtime routes, the manager's initial
payload and complete lazy output, server bundle, and total output size. Large
route and bundle lists keep their first and last entries and collapse the middle. The programmatic
RakunBunApplication.build() result exposes the same per-route asset and byte
metadata through routes.
Run the production output with rakun-bun start or bun dist/server.js.
Production assets are gzip-compressed on demand with Bun's native compressor
when the browser advertises support through Accept-Encoding. Compressed bytes
use the bounded memory cache, while development serves the original files to
keep rebuilds immediate.
await app.stop() closes HTTP, file watchers, MongoDB, hydrated collaboration
documents, and Bun's memory caches. Applications started with startRakunBun()
also perform this graceful cleanup for SIGTERM and SIGINT, including Railway
deployment shutdowns.
Public API
RakunBunConfig,RakunBunCacheOptions,RakunBunCssOptions,RakunBunDocumentProps,loadRakunConfig(), andresolveRakunConfig()createRakunBun()andstartRakunBun()RakunBunApplication.build(),.fetch(),.serve(),.invalidatePath(), and.stop()discoverRakunModules()createBunPlatform()LinkandusePathname()for Bun client navigationRakunRouteCacheandRakunRouteCacheOptions
The web config hook can replace direct core reads for a remote or test data
source. Normal monolithic applications should use bootstrap and let the
adapter call core directly.
