bknd logo
Frameworks

Next.js

Run bknd inside Next.js

Installation

To get started with Next.js and bknd you can either install the package manually, and follow the descriptions below, or use the CLI starter.

CLI Starter

Create a new Next.js CLI starter project by running the following command:

  npx bknd create -i nextjs

Manual

Create a new Next.js project by following the official guide, and then install bknd as a dependency:

npm install bknd
pnpm install bknd
yarn add bknd
bun add bknd

Configuration

When run with Node.js, a version of 22 (LTS) or higher is required. Please verify your version by running node -v, and upgrade if necessary.

Now create a bknd.config.ts file in the root of your project. If you created the project using the CLI starter, this file is already created for you.

bknd.config.ts
import type { NextjsBkndConfig } from "bknd/adapter/nextjs";

export default {
  connection: {
    url: "file:data.db",
  },
} satisfies NextjsBkndConfig;

See bknd.config.ts for more information on how to configure bknd. The NextjsBkndConfig type extends the BkndConfig type with the following additional properties:

type NextjsEnv = NextApiRequest["env"];
export type NextjsBkndConfig<Env = NextjsEnv> = FrameworkBkndConfig<Env> & {
  cleanRequest?: { searchParams?: string[] };
};

Serve the API

Create a helper file to instantiate the bknd instance and retrieve the API, importing the configuration from the bknd.config.ts file:

src/bknd.ts
import {
  type NextjsBkndConfig,
  getApp as getBkndApp,
} from "bknd/adapter/nextjs";
import { headers } from "next/headers";
import config from "../bknd.config";

export { config };

export async function getApp() {
  return await getBkndApp(config, process.env);
}

export async function getApi(opts?: { verify?: boolean }) {
  const app = await getApp();
  if (opts?.verify) {
    const api = app.getApi({ headers: await headers() });
    await api.verifyAuth();
    return api;
  }

  return app.getApi();
}

For more information about the connection object, refer to the Database guide.

Now to expose the API, create a catch-all route file at src/api/[[...bknd]]/route.ts:

src/api/[[...bknd]]/route.ts
import { config } from "@/bknd";
import { serve } from "bknd/adapter/nextjs";

// optionally, you can set the runtime to edge for better performance
// export const runtime = "edge";

const handler = serve({
  ...config,
  cleanRequest: {
    // depending on what name you used for the catch-all route,
    // you need to change this to clean it from the request.
    searchParams: ["bknd"],
  },
});

export const GET = handler;
export const POST = handler;
export const PUT = handler;
export const PATCH = handler;
export const DELETE = handler;

Enabling the Admin UI

Create a file at admin/[[...admin]]/page.client.tsx:

admin/[[...admin]]/page.client.tsx
"use client";
import dynamic from "next/dynamic";

export const Admin = dynamic(async () => (await import("bknd/ui")).Admin, {
  ssr: false,
});

We are using Colocation to prevent server-side hydration error, and the dynamic function to load Admin component only on client side.

Create a page at admin/[[...admin]]/page.tsx:

admin/[[...admin]]/page.tsx
import { getApi } from "@/bknd";
import "bknd/dist/styles.css";
import { Admin } from "./page.client";
import { Suspense } from "react";
import { redirect } from "next/navigation";

export default async function AdminPage() {
  // make sure to verify auth using headers
  const api = await getApi({ verify: true });

  // early return if not logged in
  if (!api.getUser()) {
    return redirect("/unauthorized");
  }

  return (
    <Suspense>
      <Admin
        withProvider={{ user: api.getUser() }}
        config={{
          basepath: "/admin",
          logo_return_path: "/../",
          theme: "system",
        }}
      />
    </Suspense>
  );
};

React SDK configuration

To use queries and mutation on client side, bknd provides first class support for it with it's React SDK to set it up with Next.js add the ClientProvider to your root layout

app/layout.tsx
import { ClientProvider } from "bknd/client";

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

Example usage of the API

You can use the getApi helper function we've already set up to fetch and mutate in static pages and server components:

app/page.tsx
import { getApi } from "@/bknd";

export default async function Home() {
  const api = await getApi();
  const { data: todos } = await api.data.readMany("todos", { limit: 5 });

  return (
    <ul>
      {todos.map((todo) => (
        <li key={String(todo.id)}>{todo.title}</li>
      ))}
    </ul>
  );
}

Note for Production

If your admin component is not rendering and has a error like Identifier 'b' has already been declared, you will need to add these lines to your next.config.(mjs|ts) file:

import type { NextConfig } from "next";

const nextConfig: NextConfig = {
  // ...
  // for turbopack 
  experimental: {
    turbopackMinify: false,
  },

  // for webpack
  // webpack: (config) => {
  //   config.optimization.minimize = false;
  //   return config;
  // },
};

export default nextConfig;