# Next.js

This guide describes the integration of CKBox in a Next.js application. If you prefer to jump straight to the code, you can find the [complete code of the application](https://github.com/ckbox-io/ckbox-nextjs-example) described in this guide on GitHub.

<a id="prerequisites">

## Prerequisites

Before we start, please ensure that you have a recent LTS version of Node.js installed in your system. If the tool is not available from the command line, please follow [Node.js](https://nodejs.org/en) download instructions first.

The guide assumes that we will be using Next.js v13 and its [Pages Router](https://nextjs.org/docs/pages).

<a id="creating-the-application">

## Creating the application

As a base in our example, we will create a new Next.js project using the `create-next-app` command. Select the following answers in the step-by-step wizard:

```bash
npx create-next-app@13 ckbox-nextjs-example
```

* TypeScript **Yes**
* ESLint **Yes**
* Tailwind CSS **Yes**
* `src/` directory **No**
* App Router **No**
* import alias **No**

After the project is created, you can enter the directory and start the development server:

```bash
cd ckbox-nextjs-example && npm run dev
```

<a id="environment-variables">

## Environment variables

First, let’s create an `.env.local` file and add the first entry: `NEXT_PUBLIC_URL=http://localhost:3000`. It will be later referenced in the `tokenUrl` configuration option of CKBox and CKEditor 5.

```bash
# .env.local

NEXT_PUBLIC_URL=http://localhost:3000
```

<a id="ckeditor-component-with-ckbox-plugin">

## CKEditor component with CKBox plugin

Let’s start by installing the required dependencies:

```bash
npm add ckbox ckeditor5 @ckeditor/ckeditor5-react
```

As of the time of writing this guide, the CKEditor React component [cannot be used with SSR](https://github.com/ckeditor/ckeditor5/issues/7376) in the Next.js project. Therefore, we will start by creating a simple component that will help us keep all editor’s dependencies in one place.

For the CKBox plugin with CKEditor 5, we will be using the `lark` theme, a built-in preset that brings CKBox and CKEditor together in terms of styling.

Below you can find the complete code. Please note that CKBox is a peer dependency of CKEditor and the latter relies on the `window.CKBox` object. We can ensure that CKBox is registered in the global scope by simply importing [CKBox UMD](other.md#umd-file) build via `import 'ckbox/dist/ckbox'`.

```jsx
// components/CKEditor.tsx

// This component can be used on client-side only
// Do not use it with SSR

import React from 'react';
import { CKEditor } from '@ckeditor/ckeditor5-react';
import 'ckeditor5/ckeditor5.css';
import { Bold, ClassicEditor, Italic, Underline, CKBox, PictureEditing, Autosave, BlockQuote, CKBoxImageEdit, CloudServices, Heading, List, ImageBlock, LinkImage, ImageCaption, ImageInline, ImageInsertViaUrl, Link, ImageStyle, ImageTextAlternative, ImageUpload, ImageInsert, AutoImage, Autoformat, TextTransformation } from 'ckeditor5';
import 'ckbox/dist/styles/themes/lark.css';

// CKBox is a peer dependency of CKEditor. It must be present in the global scope.
// Importing UMD build of CKBox will make sure that `window.CKBox` will be available.
import 'ckbox/dist/ckbox';

export default function CKEditorComponent() {
	const config = {
		licenseKey: 'GPL',
		ckbox: {
			tokenUrl: `${process.env.NEXT_PUBLIC_URL}/api/ckbox`,
			theme: 'lark'
		},
		toolbar: [
			'ckbox',
			'imageUpload',
			'|',
			'heading',
			'|',
			'undo',
			'redo',
			'bold',
			'italic',
			'underline',
			'|',
			'blockQuote',
			'indent',
			'link',
			'insertImage',
			'|',
			'bulletedList',
			'numberedList'
		],
		plugins: [
			Autoformat,
			AutoImage,
			Autosave,
			BlockQuote,
			Bold,
			CKBox,
			CKBoxImageEdit,
			CloudServices,
			Heading,
			ImageBlock,
			ImageCaption,
			ImageInline,
			ImageInsert,
			ImageInsertViaUrl,
			ImageStyle,
			ImageTextAlternative,
			ImageUpload,
			Italic,
			Link,
			LinkImage,
			List,
			PictureEditing,
			TextTransformation,
			Underline
		],
	};

	return (
		<>
			<style>{`.ck-editor__editable_inline { min-height: 400px; }`}</style>
			<CKEditor editor={ClassicEditor} config={config} />
		</>
	);
}
```

The above example includes a [predefined CKEditor 5 build](https://ckeditor.com/docs/ckeditor5/latest/installation/getting-started/predefined-builds.html), which contains the required [CKBox plugin](../../../../../ckeditor5/latest/features/file-management/ckbox.md#installation). Note that CKEditor is configured to use CKBox by setting the required parameters of the `ckbox` property. Please note that in the `ckbox.tokenUrl` configuration option we pass the URL of the token endpoint that will be created in the next steps of this guide.

Finally, let’s disable React’s Strict Mode since it is interfering with the CKEditor 5 React component in development mode.

```js
// next.config.js

/** @type {import('next').NextConfig} */
const nextConfig = {
	reactStrictMode: false,
};

module.exports = nextConfig;
```

<a id="ui-components">

## UI components

Let’s continue by creating a few UI components that we will be using across pages. In addition to re-usability, these components will bring a nicer look and feel to our app.

First, let’s update the `styles/globals.css` file. We will get rid of styling introduced by the `create-next-app` command and we will only leave Tailwind’s directives:

```css
/* styles/globals.css */

@tailwind base;
@tailwind components;
@tailwind utilities;
```

Then, let’s add the `Button`, `Link`, and `Page` components.

```jsx
// components/Button.tsx

import React from "react";

type Props = {
	children: React.ReactNode;
	onClick: () => void;
};

export default function Button({ children, onClick }: Props) {
	return (
		<button
			className="text-white bg-blue-600 hover:bg-blue-700 focus:ring-4 focus:ring-blue-300 font-medium rounded-lg text-sm px-4 py-1.5"
			onClick={onClick}
		>
			{children}
		</button>
	);
}
```

```jsx
// components/Link.tsx

import React from "react";
import NextLink, { type LinkProps } from "next/link";

export default function Link(
	props: Omit<React.AnchorHTMLAttributes<HTMLAnchorElement>, keyof LinkProps> &
		LinkProps & {
			children?: React.ReactNode;
		} & React.RefAttributes<HTMLAnchorElement>
) {
	return <NextLink className="text-blue-600 dark:text-blue-500 hover:underline" {...props} />;
}
```

```jsx
// components/Page.tsx

import React from "react";

type Props = {
	children: React.ReactNode;
};

export default function Page({ children }: Props) {
	return <main className="w-full max-w-4xl mx-auto py-16 h-full flex flex-col">{children}</main>;
}
```

<a id="authentication">

## Authentication

In a typical scenario access to CKBox will be restricted to [authenticated](../../configuration/authentication.md) users only. Therefore, let’s introduce a simple authentication mechanism to our app. Let’s start by installing [NextAuth.js](https://next-auth.js.org/), a popular authentication library for Next.js:

```bash
npm add next-auth
```

Then, let’s initialize API routes. We will be using `CredentialsProvider` which will allow us to introduce a simple password-based authentication. We will be using 3 test users and each one of them will be assigned a different CKBox role: `user`, `admin`, or `superadmin`.

Below you can find the complete code for authentication handlers.

```ts
// pages/api/auth/[...nextauth].ts

import NextAuth from "next-auth/next";
import CredentialsProvider from "next-auth/providers/credentials";
import type { AuthOptions, User } from "next-auth";

const dbUsers: (User & { password: string })[] = [
	{
		id: "1",
		role: "user",
		email: "user@acme.com",
		password: "testpwd123",
		name: "John",
	},
	{
		id: "2",
		role: "admin",
		email: "admin@acme.com",
		password: "testpwd123",
		name: "Joe",
	},
	{
		id: "3",
		role: "superadmin",
		email: "superadmin@acme.com",
		password: "testpwd123",
		name: "Alice",
	},
];

export const authOptions: AuthOptions = {
	providers: [
		CredentialsProvider({
			credentials: {
				email: {
					label: "Email",
				},
				password: {
					label: "Password",
				},
			},
			authorize: (credentials) => {
				const email = credentials?.email;
				const password = credentials?.password;

				const dbUser = dbUsers.find(
					(dbUser) => dbUser.email === email && dbUser.password === password
				);

				if (!dbUser) {
					throw new Error("Auth: Provide correct email and password");
				}

				return {
					id: dbUser.email,
					email: dbUser.email,
					name: dbUser.name,
					role: dbUser.role,
				};
			},
		}),
	],
	callbacks: {
		jwt: ({ token, user }) => {
			if (user) {
				token.user = user;
			}

			return token;
		},
		session: ({ token, session }) => {
			session.user = token.user;

			return session;
		},
	},
};

export default NextAuth(authOptions);
```

The authentication library expects us to set a couple of environment variables, namely `NEXTAUTH_URL` and `NEXTAUTH_SECRET`. Let’s add them to the `.env.local` file:

```bash
# .env.local

# Already set
NEXT_PUBLIC_URL=http://localhost:3000

# Added now
NEXTAUTH_URL=http://localhost:3000
NEXTAUTH_SECRET=G4JLoUae5Nke7/CZBkzFsR5NzLDKCcijsyUhTq3fQrA=
```

In production, you must generate your unique value for `NEXTAUTH_SECRET`, see [NextAuth’s documentation](https://next-auth.js.org/configuration/options#secret).

At this point, you are likely seeing type errors in the `[...nextauth].ts` file. In order to get rid of them, we must adjust NextAuth’s types as per the library’s [official guide](https://next-auth.js.org/getting-started/typescript). This step will allow us to use custom typings for `Session`, `User`, and `JWT` objects. Let’s create `auth.d.ts` file in the project’s root:

```ts
// auth.d.ts

import NextAuth, { DefaultSession } from "next-auth";

declare module "next-auth" {
	type CKBoxRole = "user" | "admin" | "superadmin";

	interface User {
		email: string;
		name: string;
		role: CKBoxRole;
	}

	interface Session {
		user: User & DefaultSession["user"];
	}
}

declare module "next-auth/jwt" {
	interface JWT {
		user: User;
	}
}
```

Next, let’s augment the main `App` component with NextAuth’s session provider.

```jsx
// pages/_app.tsx

import { SessionProvider } from "next-auth/react";
import type { AppProps } from "next/app";
import "@/styles/globals.css";

function App({ Component, pageProps: { session, ...pageProps } }: AppProps) {
	return (
		<SessionProvider session={session}>
			<Component {...pageProps} />
		</SessionProvider>
	);
}

export default App;
```

Finally, let’s add the `Nav` component that will allow users to sign in.

```jsx
// components/Nav.tsx

import React from "react";
import { signIn, signOut, useSession } from "next-auth/react";
import Link from "./Link";
import Button from "./Button";

export default function Nav() {
	const { data, status } = useSession();

	return (
		<nav className="border-b border-gray-200 py-5 relative z-20 bg-background shadow-[0_0_15px_0_rgb(0,0,0,0.1)]">
			<div className="flex items-center lg:px-6 px-8 mx-auto max-w-7xl">
				<div className="flex-1 hidden md:flex">
					<Link href="/">Home</Link>
				</div>
				<div className="flex-1 justify-end flex items-center md:flex gap-3 h-8">
					{status === "authenticated" ? (
						<>
							<span>Welcome, {data?.user?.name}!</span>
							<Button onClick={() => signOut()}>Sign out</Button>
						</>
					) : status === "loading" ? null : (
						<Button onClick={() => signIn()}>Sign in</Button>
					)}
					<Link
						href="https://github.com/ckbox-io/ckbox-nextjs-example"
						target="_blank"
						rel="noreferrer"
					>
						GitHub
					</Link>
				</div>
			</div>
		</nav>
	);
}
```

The `Nav` component will be displayed as part of the `Layout` component. Let’s add it, too.

```jsx
// components/Layout.tsx

import React from "react";
import Nav from "./Nav";

type Props = {
	children: React.ReactNode;
};

export default function Layout({ children }: Props) {
	return (
		<div className="mx-auto h-screen flex flex-col">
			<Nav />
			<div className="px-8 flex-1 bg-accents-0">{children}</div>
		</div>
	);
}
```

<a id="token-url">

## Token URL

CKBox, like other CKEditor Cloud Services, uses JWT tokens for authentication and authorization. All these tokens are generated on your application side and signed with a shared secret that you can obtain in the [Customer Portal](https://portal.ckeditor.com). For information on how to create access credentials, please refer to the [Creating access credentials](../../configuration/authentication.md#creating-access-credentials) article in the [Authentication guide](../../configuration/authentication.md)

Now that we have the required access credentials, namely: the environment ID and the access key, let’s create the token endpoint. As a base, we will use the code of the [generic token endpoint in Node.js](../../../../../cs/latest/examples/token-endpoints/nodejs.md).

First, let’s install the [jsonwebtoken](https://www.npmjs.com/package/jsonwebtoken) library for creating JWT tokens:

```bash
npm add jsonwebtoken
npm add -D @types/jsonwebtoken
```

As a reference, we will use the code of the [generic token endpoint in Node.js](../../../../../cs/latest/examples/token-endpoints/nodejs.md). Let’s wrap the logic of the Node.js token endpoint with the Next.js API handler and generate a token with the [payload required by CKBox](../../configuration/authentication.md#token-payload). Please note that access to this endpoint is limited to authenticated users only.

```ts
// pages/api/ckbox.ts

import jwt from "jsonwebtoken";
import { getServerSession } from "next-auth";
import type { NextApiRequest, NextApiResponse } from "next";
import { authOptions } from "./auth/[...nextauth]";

const CKBOX_ENVIRONMENT_ID = process.env.CKBOX_ENVIRONMENT_ID;
const CKBOX_ACCESS_KEY = process.env.CKBOX_ACCESS_KEY;

export default async function handler(req: NextApiRequest, res: NextApiResponse) {
	const session = await getServerSession(req, res, authOptions);

	if (session && CKBOX_ACCESS_KEY && CKBOX_ENVIRONMENT_ID) {
		const user = session.user;

		const payload = {
			aud: CKBOX_ENVIRONMENT_ID,
			sub: user.id,
			auth: {
				ckbox: {
					role: user.role,
				},
			},
		};

		res.send(
			jwt.sign(payload, CKBOX_ACCESS_KEY, {
				algorithm: "HS256",
				expiresIn: "1h",
			})
		);
	} else {
		res.status(401);
	}

	res.end();
}
```

As you can see on the code listed above, the access credentials required to sign JWT tokens are obtained from the environment variables. Thanks to this, you can conveniently add them to the `.env.local` file:

```bash
# .env.local

# Already set
NEXT_PUBLIC_URL=http://localhost:3000
NEXTAUTH_URL=http://localhost:3000
NEXTAUTH_SECRET=G4JLoUae5Nke7/CZBkzFsR5NzLDKCcijsyUhTq3fQrA=

# Added now
CKBOX_ENVIRONMENT_ID=REPLACE-WITH-ENVIRONMENT-ID
CKBOX_ACCESS_KEY=REPLACE-WITH-ACCESS-KEY
```

<a id="adding-ckbox-pages">

## Adding CKBox pages

CKBox can be embedded in the application in [multiple ways](../../installation.md). Examples in this guide will cover three popular scenarios:

* CKBox integrated with CKEditor 5
* CKBox used as a file picker in dialog mode
* CKBox used as a file manager in inline mode

<a id="ckbox-with-ckeditor">

### CKBox with CKEditor

Let’s use components created in previous steps to compose the `/ckeditor` page:

```jsx
// pages/ckeditor.tsx

import { GetServerSideProps } from "next";
import { getServerSession } from "next-auth";
import Layout from "@/components/Layout";
import Page from "@/components/Page";
import { authOptions } from "@/pages/api/auth/[...nextauth]";
import dynamic from "next/dynamic";

// Use client-side rendering for CKEditor component
const CKEditor = dynamic(() => import("@/components/CKEditor").then((e) => e.default), {
	ssr: false,
});

export default function CKEditorPage() {
	return (
		<Layout>
			<Page>
				<section className="flex flex-col gap-6">
					<h2 className="text-4xl font-semibold tracking-tight">CKEditor</h2>
				</section>
				<hr className="border-t border-accents-2 my-6" />
				<section className="flex flex-col gap-3 h-4/5">
					In this example CKBox is integrated with CKEditor. With CKBox plugin, CKEditor
					will upload files directly to your CKBox environment. Use icon in the top-left
					corner of the editor to open CKBox as a file picker.
					<CKEditor />
				</section>
			</Page>
		</Layout>
	);
}

// Access to this page allowed to signed-in users only
export const getServerSideProps: GetServerSideProps = async ({ req, res, resolvedUrl }) => {
	const session = await getServerSession(req, res, authOptions);

	if (!session) {
		return {
			redirect: {
				destination: `/api/auth/signin?callbackUrl=${encodeURIComponent(resolvedUrl)}`,
				permanent: false,
			},
		};
	}

	return {
		props: {
			session,
		},
	};
};
```

<a id="ckbox-as-file-picker">

### CKBox as file picker

One of the common scenarios is to use CKBox as a file picker, where the user can choose one of the files stored in the file manager. After choosing the file, we want to obtain information about the chosen files, especially their URLs. This can be achieved using the [`assets.onChoose`](../../configuration/configuration-options.md#assetsonchoose) callback passed as the CKBox’s configuration option.

In Next.js we can use CKBox directly as a React component. Currently, however, the `CKBox` official component cannot be rendered on the server. Therefore, we must ensure that it’s used on the client side only.

Let’s start by installing the required packages:

```bash
npm add @ckbox/core @ckbox/components
```

The complete page code can be found below.

```jsx
// pages/file-picker.tsx

import React from 'react';
import { GetServerSideProps } from 'next';
import dynamic from 'next/dynamic';
import { getServerSession } from 'next-auth';
import type { Asset, Props } from '@ckbox/core';
import Layout from '@/components/Layout';
import Page from '@/components/Page';
import Button from '@/components/Button';
import Link from '@/components/Link';
import { authOptions } from '@/pages/api/auth/[...nextauth]';

// CKBox cannot be currently rendered on the server
const CKBox = dynamic(() => import('@ckbox/core').then((e) => e.CKBox), {
	ssr: false
});

// Let's import stylesheet separately, since it's not bundled with the official React component
import '@ckbox/components/dist/styles/ckbox.css';

export default function FilePicker() {
	const [assets, setAssets] = React.useState<Asset[]>([]);
	const [open, setOpen] = React.useState(false);

	const handleOpen = () => {
		setOpen(true);
	};

	const handleClose = () => {
		setOpen(false);
	};

	const handleChoose = (assets: Asset[]) => {
		setOpen(false);
		setAssets(assets);
	};

	const ckboxProps: Props = {
		assets: { onChoose: handleChoose },
		dialog: { open, onClose: handleClose },
		tokenUrl: `${process.env.NEXT_PUBLIC_URL}/api/ckbox`
	};

	return (
		<Layout>
			<Page>
				<section className="flex flex-col gap-6">
					<h2 className="text-4xl font-semibold tracking-tight">
						File Picker
					</h2>
				</section>
				<hr className="border-t border-accents-2 my-6" />
				<section className="flex flex-col gap-3">
					One of the common scenarios is to use CKBox as a file
					picker, where the user can choose one of the files stored in
					the file manager. After choosing the file, we want to obtain
					information about the chosen files, especially their URLs.
					<div>
						<Button onClick={handleOpen}>Choose assets</Button>
					</div>
					<CKBox {...ckboxProps} />
				</section>
				<section className="flex flex-col gap-3">
					<ul>
						{assets.map(({ data }) => {
							const name = `${data.name}.${data.extension}`;
							const content = data.url ? (
								<Link
									target="_blank"
									rel="noreferrer"
									href={data.url}
								>
									{name}
								</Link>
							) : (
								name
							);

							return <li key={data.id}>{content}</li>;
						})}
					</ul>
				</section>
			</Page>
		</Layout>
	);
}

// Access to this page allowed to signed-in users only
export const getServerSideProps: GetServerSideProps = async ({
	req,
	res,
	resolvedUrl
}) => {
	const session = await getServerSession(req, res, authOptions);

	if (!session) {
		return {
			redirect: {
				destination: `/api/auth/signin?callbackUrl=${encodeURIComponent(
					resolvedUrl
				)}`,
				permanent: false
			}
		};
	}

	return {
		props: {
			session
		}
	};
};
```

At this point, it’s also important to adjust the Webpack config of Next.js. We must ensure that `@ckbox/*` dependencies are not code-split. Below is the complete `next.config.js` file.

```js
// next.config.js

/** @type {import('next').NextConfig} */
const nextConfig = {
	reactStrictMode: false,
	webpack: (config, options) => {
		if (!options.isServer) {
			config.optimization.splitChunks.cacheGroups = {
				...config.optimization.splitChunks.cacheGroups,
				ckbox: {
					test: /@ckbox\//,
					minChunks: 1,
					priority: 50,
				},
			};
		}

		return config;
	},
};

module.exports = nextConfig;
```

<a id="ckbox-in-inline-mode">

### CKBox in inline mode

To start CKBox in inline mode, you can simply mount it in the desired place of your app. This is the default embedding mode for CKBox, so no additional configuration props besides `tokenUrl` are required. CKBox will occupy as much space as allowed by its parent element.

```jsx
// pages/inline.tsx

import { GetServerSideProps } from "next";
import { getServerSession } from "next-auth";
import dynamic from "next/dynamic";
import Layout from "@/components/Layout";
import Page from "@/components/Page";
import { authOptions } from "@/pages/api/auth/[...nextauth]";

// CKBox cannot be currently rendered on the server
const CKBox = dynamic(() => import("@ckbox/core").then((e) => e.CKBox), {
	ssr: false,
});

// Let's import stylesheet separately, since it's not bundled with the React component
import "@ckbox/components/dist/styles/ckbox.css";

export default function Inline() {
	return (
		<Layout>
			<Page>
				<section className="flex flex-col gap-6">
					<h2 className="text-4xl font-semibold tracking-tight">Inline</h2>
				</section>
				<hr className="border-t border-accents-2 my-6" />
				<section className="flex flex-col gap-3 flex-1">
					<p>
						To start CKBox in inline mode, you can instantiate it in an arbitrary
						container. CKBox will respect height and width of the container.
					</p>
					<CKBox tokenUrl={`${process.env.NEXT_PUBLIC_URL}/api/ckbox`} />
				</section>
			</Page>
		</Layout>
	);
}

// Access to this page allowed to signed-in users only
export const getServerSideProps: GetServerSideProps = async ({ req, res, resolvedUrl }) => {
	const session = await getServerSession(req, res, authOptions);

	if (!session) {
		return {
			redirect: {
				destination: `/api/auth/signin?callbackUrl=${encodeURIComponent(resolvedUrl)}`,
				permanent: false,
			},
		};
	}

	return {
		props: {
			session,
		},
	};
};
```

<a id="home-page">

## Home page

Finally, let’s tweak the home page so that it displays links to all pages created above.

```jsx
// pages/index.tsx

import Layout from "@/components/Layout";
import Link from "@/components/Link";
import Page from "@/components/Page";

export default function Home() {
	return (
		<Layout>
			<Page>
				<section className="flex flex-col gap-6">
					<h2 className="text-3xl font-semibold tracking-tight">
						CKBox integration with Next.js
					</h2>
				</section>
				<hr className="border-t border-accents-2 my-6" />
				<section className="flex flex-col gap-3">
					<p>Below you can find example integrations of CKBox.</p>
					<p>
						In a typical scenario access to CKBox will be restricted to authenticated
						users only. Therefore, each sample is restricted to signed in users only.
						Use different credentials to unlock various CKBox roles. See available users{" "}
						<Link
							href="https://github.com/ckbox-io/ckbox-nextjs-example/blob/main/pages/api/auth/%5B...nextauth%5D.ts"
							target="_blank"
							rel="noreferrer"
						>
							here
						</Link>
						.
					</p>
					<div className="flex-1 hidden md:flex gap-2">
						<ol>
							<li>
								<Link href="/inline">Inline mode</Link>
							</li>
							<li>
								<Link href="/file-picker">File picker</Link>
							</li>
							<li>
								<Link href="/ckeditor">CKEditor</Link>
							</li>
						</ol>
					</div>
				</section>
			</Page>
		</Layout>
	);
}
```

<a id="congratulations">

## Congratulations

Congratulations on completing the guide! You can now access the app in development mode:

```bash
npm run dev
```

Or use the production build:

```bash
npm run build && npm start
```

<a id="complete-code">

## Complete code

On GitHub, you can find the [complete code of the application](https://github.com/ckbox-io/ckbox-nextjs-example) described in this guide.

---

Full index of the CKBox documentation: [llms.txt](../../../../llms.txt)
