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 languageESLint
as the linterTailwind CSS
as the CSS frameworksrc
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:
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-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 thesrc
property:
import logo from "./logo.png";
<FirebaseLoginView
logo={logo.src}/>