Skip to main content
Version: 3.0.0-beta

Using FireCMS with Next.js

You can use FireCMS with Next.js. FireCMS is a React library, so you can use it with any React framework.

In the case of Next.js, you are restricted to running FireCMS on the client side, as Next.js does not support server side rendering of some of the React components used by FireCMS.

Let's build an app using FireCMS and Next.js, with the app router configured to delegate all the routes starting with /cms to FireCMS.

Create a Next.js project

Start by creating your Next.js project:

npx create-next-app@latest

Select:

  • TypeScript as the language
  • ESLint as the linter
  • Tailwind CSS as the CSS framework
  • src as the root directory
  • Yes to the app router prompt
  • Yes to customize the default import alias (optional)

Install FireCMS

Then we are going to install FireCMS PRO. Note that we will not be adding all the plugins like the collection editor or data enhancement, but you can add them as needed.

Then install FireCMS and its dependencies:

yarn add firebase@^10 @firecms/core@^3.0.0-beta @firecms/firebase@^3.0.0-beta @firecms/editor@^3.0.0-beta react-router@^6 react-router-dom@^6 @tailwindcss/typography typeface-rubik @fontsource/jetbrains-mono

Now let's import the tailwind config of FireCMS. Add the FireCMS preset tailwind.config.js, as well as the content paths to FireCMS source code, so the right tailwind classes are picked.

import fireCMSConfig from "@firecms/ui/tailwind.config.js";
import type { Config } from "tailwindcss";

const config: Config = {
presets: [fireCMSConfig],
content: [
"./src/pages/**/*.{js,ts,jsx,tsx,mdx}",
"./src/components/**/*.{js,ts,jsx,tsx,mdx}",
"./src/app/**/*.{js,ts,jsx,tsx,mdx}",
"./src/cms/**/*.{js,ts,jsx,tsx,mdx}",
"./node_modules/@firecms/**/*.{js,ts,jsx,tsx}"
]
};
export default config;

Disable yarn pnp (optional)

We prefer disabling yarn pnp for this project. You can do this by creating the file .yarnrc in the root of your project with the following content:

nodeLinker: node-modules

Configuring the App router

Next.js uses a file based router. In this guide, we will be creating the FireCMS app in the /cms route, but you can customize this to your needs.

FireCMS uses react-router, so we need to configure Next.js to delegate all the routes starting with /cms to FireCMS.

In our app folder, we create a folder called cms and inside it another one called [[...path]]. This will match any route starting with /cms.

Then create the file cms/[[...path]]/page.tsx with the following content:

important

If you are not running FireCMS in the root path of you app, you need to set the basePath prop to the path where you are running it. In this case, we are running it in /cms.

"use client";
import { FireCMSApp } from "@/cms/FireCMSApp";
import { BrowserRouter } from "react-router-dom";

export default function CMS() {
return <BrowserRouter basename={"/cms"}>
<FireCMSApp/>
</BrowserRouter>;
}

Creating the CMS

Now let's create the FireCMS components. Create the file ./src/cms/FireCMSApp.tsx with the following content. Remember to replace the firebaseConfig with your own Firebase configuration.

"use client";
import React, { useCallback } from "react";

import "./index.css";
import "typeface-rubik";
import "@fontsource/jetbrains-mono";

import {
AppBar,
buildCollection,
CircularProgressCenter,
Drawer,
FireCMS,
ModeControllerProvider,
NavigationRoutes,
Scaffold,
SideDialogs,
SnackbarProvider,
useBuildLocalConfigurationPersistence,
useBuildModeController,
useBuildNavigationController,
useValidateAuthenticator
} from "@firecms/core";
import {
FirebaseAuthController,
FirebaseLoginView,
FirebaseSignInProvider,
useFirebaseAuthController,
useFirebaseStorageSource,
useFirestoreDelegate,
useInitialiseFirebase,
} from "@firecms/firebase";
import { useImportPlugin } from "@firecms/data_import";
import { useExportPlugin } from "@firecms/data_export";
import { useBuildUserManagement, userManagementAdminViews, useUserManagementPlugin } from "@firecms/user_management";
import { useFirestoreCollectionsConfigController } from "@firecms/collection_editor_firebase";
import { mergeCollections, useCollectionEditorPlugin } from "@firecms/collection_editor";

//TODO: replace with your own Firebase config
export const firebaseConfig = {
//...
};

const categories = {
fiction: "Fiction",
drama: "Drama",
"fantasy-fiction": "Fantasy fiction",
history: "History",
religion: "Religion",
"self-help": "Self-Help",
"comics-graphic-novels": "Comics & Graphic Novels",
"juvenile-fiction": "Juvenile Fiction",
philosophy: "Philosophy",
fantasy: "Fantasy",
education: "Education",
science: "Science",
medical: "Medical",
cooking: "Cooking",
travel: "Travel"
};

const booksCollection = buildCollection({
name: "Books",
singularName: "Book",
id: "books",
path: "books",
icon: "MenuBook",
group: "Content",
textSearchEnabled: true,
description: "Example of a books collection that allows data enhancement through the use of the **OpenAI plugin**",
properties: {
title: {
name: "Title",
validation: { required: true },
dataType: "string"
},
authors: {
name: "Authors",
dataType: "string"
},
description: {
name: "Description",
dataType: "string",
multiline: true
},
spanish_description: {
name: "Spanish description",
dataType: "string",
multiline: true
},
thumbnail: {
name: "Thumbnail",
dataType: "string",
url: "image"
},
category: {
name: "Category",
dataType: "string",
enumValues: categories
},
tags: {
name: "Tags",
dataType: "array",
of: {
dataType: "string"
}
},
published_year: {
name: "Published Year",
dataType: "number",
validation: {
integer: true,
min: 0
}
},
num_pages: {
name: "Num pages",
dataType: "number"
},
created_at: {
name: "Created at",
dataType: "date",
autoValue: "on_create"
}
}
});

export function FireCMSApp() {

const {
firebaseApp,
firebaseConfigLoading,
configError
} = useInitialiseFirebase({
firebaseConfig
});

// Controller used to manage the dark or light color mode
const modeController = useBuildModeController();

const signInOptions: FirebaseSignInProvider[] = ["google.com"];

// Controller for saving some user preferences locally.
const userConfigPersistence = useBuildLocalConfigurationPersistence();

// Delegate used for fetching and saving data in Firestore
const firestoreDelegate = useFirestoreDelegate({
firebaseApp
});

// Controller used for saving and fetching files in storage
const storageSource = useFirebaseStorageSource({
firebaseApp
});

const collectionConfigController = useFirestoreCollectionsConfigController({
firebaseApp
});

// controller in charge of user management
const userManagement = useBuildUserManagement({
dataSourceDelegate: firestoreDelegate,
});

// Controller for managing authentication
const authController: FirebaseAuthController = useFirebaseAuthController({
firebaseApp,
signInOptions,
loading: userManagement.loading,
defineRolesFor: userManagement.defineRolesFor
});

const {
authLoading,
canAccessMainView,
notAllowedError
} = useValidateAuthenticator({
disabled: userManagement.loading,
authenticator: userManagement.authenticator,
authController,
// authenticator: myAuthenticator,
dataSourceDelegate: firestoreDelegate,
storageSource
});

const collectionsBuilder = useCallback(() => {
const collections = [
booksCollection,
// Your collections here
];
return mergeCollections(collections, collectionConfigController.collections ?? []);
}, [collectionConfigController.collections]);

const navigationController = useBuildNavigationController({
basePath: "/",
collections: collectionsBuilder,
collectionPermissions: userManagement.collectionPermissions,
adminViews: userManagementAdminViews,
authController,
dataSourceDelegate: firestoreDelegate
});

const userManagementPlugin = useUserManagementPlugin({ userManagement });

const importPlugin = useImportPlugin();
const exportPlugin = useExportPlugin();

const collectionEditorPlugin = useCollectionEditorPlugin({
collectionConfigController
});

if (firebaseConfigLoading || !firebaseApp) {
return <><CircularProgressCenter/></>;
}

if (configError) {
return <>{configError}</>;
}
return (
<SnackbarProvider>
<ModeControllerProvider value={modeController}>
<FireCMS
navigationController={navigationController}
authController={authController}
userConfigPersistence={userConfigPersistence}
dataSourceDelegate={firestoreDelegate}
storageSource={storageSource}
plugins={[importPlugin, exportPlugin, userManagementPlugin, collectionEditorPlugin]}
>
{({
context,
loading
}) => {

if (loading || authLoading) {
return <CircularProgressCenter size={"large"}/>;
}
if (!canAccessMainView) {
return <FirebaseLoginView authController={authController}
firebaseApp={firebaseApp}
signInOptions={signInOptions}
notAllowedError={notAllowedError}/>;
}

return <Scaffold
autoOpenDrawer={false}>
<AppBar title={"My demo app"}/>
<Drawer/>
<NavigationRoutes/>
<SideDialogs/>
</Scaffold>;
}}
</FireCMS>
</ModeControllerProvider>
</SnackbarProvider>
);

}

Import the default FireCMS styles

Create a file called index.css in the ./src/cms folder with the following content:

@import "@firecms/ui/index.css";
@tailwind base;
@tailwind components;
@tailwind utilities;

:root {
--fcms-primary: #0070F4;
--fcms-primary-dark: #0061e6;
--fcms-primary-bg: #0061e610;
--fcms-secondary: #FF5B79;
}

a {
@apply text-blue-600 dark:text-blue-400 dark:hover:text-blue-600 hover:text-blue-800
}

Run

Then simply run:

yarn dev

and navigate to http://localhost:3000/cms to see your FireCMS app running.

Some considerations

  • Images are loaded differently in Next.js. You get a StaticImageData instead of the image URL (as in vite). You can use it in FireCMS components that expect a URL like, using the src property:
import logo from "./logo.png";

<FirebaseLoginView
logo={logo.src}/>
Sign up to our newsletter to get the latest news and updates. No spam!