Next.js
Refine provides router bindings and utilities for Next.js. This package will provide easy integration between Refine and Next.js for both existing projects and new projects without giving up the benefits of Next.js.
- npm
- pnpm
- yarn
npm i @refinedev/nextjs-router
pnpm add @refinedev/nextjs-router
yarn add @refinedev/nextjs-router
We recommend using create refine-app
to initialize your Refine projects. It configures the project according to your needs including SSR with Next.js!
- npm
- pnpm
- yarn
npm create refine-app@latest -- -o refine-nextjs my-refine-nextjs-app
pnpm create refine-app@latest -- -o refine-nextjs my-refine-nextjs-app
yarn create refine-app@latest -- -o refine-nextjs my-refine-nextjs-app
Only supports yarn@1 version.
Refer to the Router Provider documentation for detailed information. →
Basic Usage
/app
Directory
We'll use the routerProvider
from @refinedev/nextjs-router
to set up the router bindings for Refine. We'll define the action routes for our resources in the resources
array and define our pages in the app
directory.
We'll create four routes for our resources:
app/posts/page.tsx
- List page for postsapp/posts/show/[id]/page.tsx
- Detail page for postsapp/categories/page.tsx
- List page for categoriesapp/categories/show/[id]/page.tsx
- Detail page for categories
And we'll create one route for the index and use it to redirect to the posts
resource:
app/page.tsx
- Index page
Let's start with the initialization of the Refine app in the app/layout.tsx
file:
import { Refine } from "@refinedev/core";
import dataProvider from "@refinedev/simple-rest";
import routerProvider from "@refinedev/nextjs-router";
import { Layout } from "components/Layout";
export default function RootLayout({
children,
}: {
children: React.ReactNode;
}) {
return (
<html lang="en">
<body>
<Refine
dataProvider={dataProvider("https://api.fake-rest.refine.dev")}
routerProvider={routerProvider}
resources={[
{
name: "posts",
list: "/posts",
show: "/posts/show/:id",
},
{
name: "categories",
list: "/categories",
show: "/categories/show/:id",
},
]}
>
{/* We're defining `Layout` here but you might want to have different layouts per your page. */}
{/* This is totally fine for Refine, you can place your Layout wherever you like. */}
<Layout>{children}</Layout>
</Refine>
</body>
</html>
);
}
🚨 Refer to the FAQ section for instructions on using providers in both client and server components.
Next.js uses the bracket syntax (
[param]
) for dynamic routes but Refine uses the colon syntax (:param
) for route parameters. This won't cause any problems since Refine only uses the colon syntax as an indicator for route parameters and the communication between Refine and the router is handled by therouterProvider
prop.Since
<Refine />
is heavily using React context and React state, it's needed to be a client component, and we can't pass functions into the Refine component unless we mark them asclient
functions(dataProvider, authProvider etc.) with"use client";
directive. See the solution for this in the FAQ section.
Your action definitions in the resources can contain additional parameters and nested routes. Passing these parameters when navigating to the pages are handled by the current available parameters and the meta
props of the related hooks and components.
Refine supports route parameters defined with :param
syntax. You can use these parameters in your action definitions and create your routes accordingly. For example, if you have a posts
resource and you want to create a route for the show
action of a specific post, you can define the show
action as /posts/show/:id
and use the id
parameter in your component.
Now we can create our pages in the app
directory:
"use client";
import { useTable } from "@refinedev/core";
import Link from "next/link";
type IPost = {
id: string;
title: string;
description: string;
};
export default function PostList() {
// `posts` resource will be inferred from the route.
// Because we've defined `/posts` as the `list` action of the `posts` resource.
const {
tableQuery: { data, isLoading },
} = useTable<IPost>();
const tableData = data?.data;
return (
<div>
{isLoading && <p>Loading...</p>}
{!isLoading && (
<ul>
{tableData?.map((post) => (
<li key={post.id}>
<Link href={`/posts/show/${post.id}`}>{post.title}</Link>
</li>
))}
</ul>
)}
</div>
);
}
"use client";
import { useShow } from "@refinedev/core";
type IPost = {
id: string;
title: string;
description: string;
}
export default function PostShow() {
// `posts` resource and the `id` will be inferred from the route.
// Because we've defined `/posts/show/:id` as the `show` action of the `posts` resource.
const { queryResult: { data, isLoading } } = useShow<IPost>();
const postData = data?.data;
return (
<div>
{isLoading && <p>Loading...</p>}
{!isLoading && (
<h1>{postData?.title}</h1>
<p>{postData?.description}</p>
)}
</div>
);
}
"use client";
import { useTable } from "@refinedev/core";
import Link from "next/link";
type ICategory = {
id: string;
label: string;
};
export default function CategoryList() {
// `categories` resource will be inferred from the route.
// Because we've defined `/categories` as the `list` action of the `categories` resource.
const {
tableQuery: { data, isLoading },
} = useTable<ICategory>();
const tableData = data?.data;
return (
<div>
{isLoading && <p>Loading...</p>}
{!isLoading && (
<ul>
{tableData?.map((category) => (
<li key={category.id}>
<Link href={`/categories/show/${category.id}`}>
{category.label}
</Link>
</li>
))}
</ul>
)}
</div>
);
}
"use client";
import { useShow } from "@refinedev/core";
type ICategory = {
id: string;
label: string;
};
export default function CategoryShow() {
// `categories` resource and the `id` will be inferred from the route.
// Because we've defined `/categories/show/:id` as the `show` action of the `categories` resource.
const {
queryResult: { data, isLoading },
} = useShow<ICategory>();
const categoryData = data?.data;
return (
<div>
<h1>{categoryData?.label}</h1>
</div>
);
}
Now, we'll use NavigateToResource
component to redirect to the posts
resource when the user visits the home page.
Even though we're using the NavigateToResource
component, when using Next.js it's better to handle such redirect operations in the server side rather than the client side. You can use the getServerSideProps
function to redirect the user to the /posts
.
"use client";
import { NavigateToResource } from "@refinedev/nextjs-router";
export default function IndexPage() {
return <NavigateToResource />;
}
/pages
Directory
We'll use the routerProvider
from @refinedev/nextjs-router
to set up the router bindings for Refine. We'll define the action routes for our resources in the resources
array and define our pages in the pages
directory.
We'll create four pages for our resources:
pages/posts/index.tsx
- List page for postspages/posts/show/[id].tsx
- Detail page for postspages/categories/index.tsx
- List page for categoriespages/categories/show/[id].tsx
- Detail page for categories
And we'll create one page for the index route and use it to redirect to the posts
resource:
pages/index.tsx
- Index page
Let's start with the initialization of the Refine app in the _app.tsx
file:
import { Refine } from "@refinedev/core";
import dataProvider from "@refinedev/simple-rest";
import routerProvider from "@refinedev/nextjs-router/pages";
import { Layout } from "components/Layout";
function App({ Component, pageProps }: AppProps): JSX.Element {
return (
<Refine
dataProvider={dataProvider("https://api.fake-rest.refine.dev")}
routerProvider={routerProvider}
resources={[
{
name: "posts",
list: "/posts",
show: "/posts/show/:id",
},
{
name: "categories",
list: "/categories",
show: "/categories/show/:id",
},
]}
>
<Layout>
<Component {...pageProps} />
</Layout>
</Refine>
);
}
Next.js uses the bracket syntax ([param]
) for dynamic routes but Refine uses the colon syntax (:param
) for route parameters. This won't cause any problems since Refine only uses the colon syntax as an indicator for route parameters and the communication between Refine and the router is handled by the routerProvider
prop.
Your action definitions in the resources can contain additional parameters and nested routes. Passing these parameters when navigating to the pages are handled by the current available parameters and the meta
props of the related hooks and components.
Refine supports route parameters defined with :param
syntax. You can use these parameters in your action definitions and create your routes accordingly. For example, if you have a posts
resource and you want to create a route for the show
action of a specific post, you can define the show
action as /posts/show/:id
and use the id
parameter in your component.
Now we can create our pages in the pages
directory:
import { useTable } from "@refinedev/core";
import Link from "next/link";
type IPost = {
id: string;
title: string;
description: string;
};
export default function PostList() {
// `posts` resource will be inferred from the route.
// Because we've defined `/posts` as the `list` action of the `posts` resource.
const {
tableQuery: { data, isLoading },
} = useTable<IPost>();
const tableData = data?.data;
return (
<div>
{isLoading && <p>Loading...</p>}
{!isLoading && (
<ul>
{tableData?.map((post) => (
<li key={post.id}>
<Link href={`/posts/show/${post.id}`}>{post.title}</Link>
</li>
))}
</ul>
)}
</div>
);
}
import { useShow } from "@refinedev/core";
type IPost = {
id: string;
title: string;
description: string;
}
export default function PostShow() {
// `posts` resource and the `id` will be inferred from the route.
// Because we've defined `/posts/show/:id` as the `show` action of the `posts` resource.
const { queryResult: { data, isLoading } } = useShow<IPost>();
const postData = data?.data;
return (
<div>
{isLoading && <p>Loading...</p>}
{!isLoading && (
<h1>{postData?.title}</h1>
<p>{postData?.description}</p>
)}
</div>
);
}
import { useTable } from "@refinedev/core";
import Link from "next/link";
type ICategory = {
id: string;
label: string;
};
export default function CategoryList() {
// `categories` resource will be inferred from the route.
// Because we've defined `/categories` as the `list` action of the `categories` resource.
const {
tableQuery: { data, isLoading },
} = useTable<ICategory>();
const tableData = data?.data;
return (
<div>
{isLoading && <p>Loading...</p>}
{!isLoading && (
<ul>
{tableData?.map((category) => (
<li key={category.id}>
<Link href={`/categories/show/${category.id}`}>
{category.label}
</Link>
</li>
))}
</ul>
)}
</div>
);
}
import { useShow } from "@refinedev/core";
type ICategory = {
id: string;
label: string;
};
export default function CategoryShow() {
// `categories` resource and the `id` will be inferred from the route.
// Because we've defined `/categories/show/:id` as the `show` action of the `categories` resource.
const {
queryResult: { data, isLoading },
} = useShow<ICategory>();
const categoryData = data?.data;
return (
<div>
<h1>{categoryData?.label}</h1>
</div>
);
}
Now, we'll use NavigateToResource
component to redirect to the posts
resource when the user visits the home page.
Even though we're using the NavigateToResource
component, when using Next.js it's better to handle such redirect operations in the server side rather than the client side. You can use the getServerSideProps
function to redirect the user to the /posts
.
import { NavigateToResource } from "@refinedev/nextjs-router/pages";
export default function Home() {
return <NavigateToResource />;
}
Additional Components
@refinedev/nextjs-router
package also includes some additional components that can be useful in some cases.
NavigateToResource
A basic component to navigate to a resource page. It is useful when you want to navigate to a resource page at the index route of your app.
In app/page.tsx
"use client";
import { NavigateToResource } from "@refinedev/nextjs-router";
export default function IndexPage() {
return <NavigateToResource />;
}
In pages/index.tsx
import { NavigateToResource } from "@refinedev/nextjs-router/pages";
export default function IndexPage() {
return <NavigateToResource />;
}
Properties
resource
(optional) - The name of the resource to navigate to. It will redirect to the first list
route in the resources
array if not provided.
meta
(optional) - The meta object to use if the route has parameters in it. The parameters in the current location will also be used to compose the route but meta
will take precedence.
UnsavedChangesNotifier
🚨 Note that this component currently only works in the
pages
directory due to limitations of thenext/navigation
and missing events.
This component enables the warnWhenUnsavedChanges
feature of Refine. It will show a warning message when user tries to navigate away from the current page without saving the changes. Also checks for beforeunload
event to warn the user when they try to close the browser tab or window.
Place this component inside the <Refine>
components children to enable this feature.
import { Refine } from "@refinedev/core";
import { UnsavedChangesNotifier } from "@refinedev/nextjs-router/pages";
function App({ Component, pageProps }: AppProps): JSX.Element {
return (
<Refine
/* ... */
>
<Component {...pageProps} />
<UnsavedChangesNotifier />
</Refine>
);
}
This feature is not working in appDir
mode in Next.js due to limitations of the next/navigation
and missing events.
Properties
translationKey
(optional) - The translation key for the warning message. Default value is warnWhenUnsavedChanges
. Useful when you use an i18n provider.
message
(optional) - The warning message. Default value is Are you sure you want to leave? You have unsaved changes.
Useful when you don't use an i18n provider.
parseTableParams
This function can be used to parse the query parameters of current route. It can be useful when you want to use the query parameters in your server side functions (getData
) to fetch the data such as persisting the table state
For server components, import it from @refinedev/nextjs-router/parse-table-params
.
import parseTableParams from "@refinedev/nextjs-router/parse-table-params";
If you want to use it in your client components, you can import it directly from @refinedev/nextjs-router
.
import { parseTableParams } from "@refinedev/nextjs-router";
DocumentTitleHandler
🚨 Note that this component currently only works in the
pages
directory.
This component will generate the document title for the current page.By default, it follows a set of predefined rules to generate titles based on the provided props. However, it also offers the flexibility to customize the title generation process by providing a custom handler
function.
The default title generation rules are as follows:
- list :
Posts | Refine
- edit :
#{id} Edit Post | Refine
- show :
#{id} Show Post | Refine
- create :
Create new Post | Refine
- clone :
#{id} Clone Post | Refine
- default :
Refine
const App = () => {
return (
<BrowserRouter>
<Refine
/* ... */
>
{/* ... */}
<DocumentTitleHandler />
</Refine>
</BrowserRouter>
);
};
Properties
handler
(optional) - The function that will get invoked in every location change. It will receive an object with the following properties:
pathname
: The current URL pathname.resource
: The resource being displayed (e.g., "posts").action
: The action being performed (e.g., "edit", "show", "create").autoGeneratedTitle
: The default auto-generated title based on the predefined rules.params
: An object containing URL parameters, including theid
parameter if present.
Inside the handler function, you can dynamically generate the document title based on the provided properties and any additional logic required. The function should return the generated title.
const customTitleHandler = ({ resource, action, params }) => {
let title = "Custom default"; // Default title
if (resource && action) {
title = `${resource} ${action} ${params.id}`;
}
return title;
};
// Usage
<DocumentTitleHandler handler={customTitleHandler} />;
Hooks
useDocumentTitle
Note that this hook doesn't support SSR. It will only set the document title in the client side.
This hook allows you to set the document title for the current page. It can be used in any component that is a child of the <Refine>
component.
import { useDocumentTitle } from "@refinedev/nextjs-router/pages";
const PostList = () => {
useDocumentTitle("Posts | Refine");
return <List>{/* ... */}</List>;
};
This hook can take an object as an argument with i18nKey
. This key will be used to translate the title using the i18n provider.
import { useDocumentTitle } from "@refinedev/nextjs-router/pages";
const PostList = () => {
useDocumentTitle({ i18nKey: "documentTitle.posts.list" });
return <List>{/* ... */}</List>;
};
This hook also returns a function that can be used to set the document title dynamically.
import { useDocumentTitle } from "@refinedev/nextjs-router/pages";
const PostList = () => {
const setTitle = useDocumentTitle();
useEffect(() => {
setTitle("Posts | Refine");
}, []);
return <List>{/* ... */}</List>;
};
Authentication
In Next.js you can achieve authentication control in multiple ways;
For page level authentication, server-side approach is recommended.
Client side
On the client-side, you can wrap your pages with Authenticated
component from @refinedev/core
to protect your pages from unauthenticated access.
Server Side
For page level authentication, server-side approach is recommended.
First let's create our AuthProvider
let's install the nookies
packages in our project.
- npm
- pnpm
- yarn
npm i @refinedev/nookies
pnpm add @refinedev/nookies
yarn add @refinedev/nookies
We will set/destroy cookies in the login
, logout
and check
functions of our AuthProvider
.
import { AuthProvider } from "@refinedev/core";
import nookies from "nookies";
const mockUsers = [
{
username: "admin",
roles: ["admin"],
},
{
username: "editor",
roles: ["editor"],
},
];
const COOKIE_NAME = "user";
export const authProvider: AuthProvider = {
login: ({ username, password, remember }) => {
// Suppose we actually send a request to the back end here.
const user = mockUsers.find((item) => item.username === username);
if (user) {
nookies.set(null, COOKIE_NAME, JSON.stringify(user), {
maxAge: remember ? 30 * 24 * 60 * 60 : undefined,
});
return {
success: true,
};
}
return {
success: false,
};
},
logout: () => {
nookies.destroy(null, COOKIE_NAME);
return {
success: true,
redirectTo: "/login",
};
},
onError: (error) => {
if (error && error.statusCode === 401) {
return {
error: new Error("Unauthorized"),
logout: true,
redirectTo: "/login",
};
}
return {};
},
check: async (context) => {
let user = undefined;
if (context) {
// for SSR
const cookies = nookies.get(context);
user = cookies[COOKIE_NAME];
} else {
// for CSR
const cookies = nookies.get(null);
user = cookies[COOKIE_NAME];
}
if (!user) {
return {
authenticated: false,
error: {
message: "Check failed",
name: "Unauthorized",
},
logout: true,
redirectTo: "/login",
};
}
return {
authenticated: true,
};
},
getPermissions: async () => {
return null;
},
getIdentity: async () => {
return null;
},
};
After creating our AuthProvider
, we can call it from server-side to check if the user is authenticated or not.
app
directory
You can use your authProvider's check function inside your server components. To do this, you can use normal async function and use it in your server component.
🚨 Refer to the FAQ section for instructions on using providers in both client and server components.
import { authProvider } from "@providers/auth-provider";
import { redirect } from "next/navigation";
export default async function IndexPage() {
const { hasAuth, hasPermission, data } = await getData();
if (!hasAuth) {
return redirect("/login");
}
return (
<div>
<h1>Posts</h1>
<ul>
{data?.map((post: any) => (
<li key={post.id}>{post.title}</li>
))}
</ul>
</div>
);
}
async function getData() {
const hasAuth = await authProvider.check();
let data = null;
if (hasAuth && hasPermission) {
data = await dataProvider.getList({
resource: "posts",
});
}
return {
hasAuth,
data,
};
}
pages
directory
On the server-side of pages router, you can use your authProvider
's check
function inside server side functions (getServerSideProps
) to redirect unauthenticated users to other pages like login...
import { authProvider } from "@providers/auth-provider";
export const getServerSideProps = async (context) => {
// We've handled the SSR case in our `check` function by sending the `context` as parameter.
const { authenticated, redirectTo } = await authProvider.check(context);
if (!authenticated) {
context.res.statusCode = 401;
context.res.end();
}
if (!authenticated && redirectTo) {
return {
redirect: {
destination: redirectTo,
permanent: false,
},
};
}
return {
props: {
authenticated,
},
};
};
This needs to be done for all the routes that we want to protect.
Access Control
In Next.js you can achieve access control in multiple ways;
On the client-side you can wrap your pages with CanAccess
component from @refinedev/core
to protect your pages from unauthorized access.
And on the server-side you can use your accessControlProvider
's can
function inside server side functions (getServerSideProps
) to redirect unauthorized users to other pages..
For page level access control, server-side approach is recommended.
First, let's build our AccessControlProvider
export const accessControlProvider = {
can: async ({ resource, action, params }) => {
// You can also access resource object directly with `params.resource` and use any useful meta data in `params.resource.meta`.
// const resourceName = params?.resource?.name;
// const anyUsefulMeta = params?.resource?.meta?.yourUsefulMeta;
if (resource === "posts" && action === "edit") {
return {
can: false,
reason: "Unauthorized",
};
}
return { can: true };
},
};
Client Side
For client-side, you can wrap your pages with CanAccess
component from @refinedev/core
to protect your pages from unauthorized access.
import { CanAccess } from "@refinedev/core";
export const MyPage = () => (
<CanAccess>
<div>{/* ... */}</div>
</CanAccess>
);
Server Side
app
directory
You can use your accessControlProvider
's can
function inside your server components. To do this, you can use normal async function and use it in your server component.
import { accessControlProvider } from "src/accessControlProvider";
export default async function PostList() {
const { can } = await getData();
if (!can) {
return <h1>Unauthorized</h1>;
}
return (
<div>
<h1>Posts</h1>
</div>
);
}
async function getData() {
const { can } = await accessControlProvider.can({
resource: "posts",
action: "list",
});
return {
can,
};
}
🚨 Refer to the FAQ section for instructions on using providers in both client and server components.
To learn more about how to fetch data in server components, you can check the data fetching documentation of Next.js.
pages
directory
You can use your accessControlProvider
's can
function inside getServerSideProps
to redirect unauthorized users to other pages.
Then, let's create our posts page and use the accessControlProvider
to check if the user can list the posts.
import { accessControlProvider } from "src/accessControlProvider";
export default async function PostList() {
const { can } = await getData();
if (!can) {
return <h1>Unauthorized</h1>;
}
return (
<div>
<h1>Posts</h1>
</div>
);
}
async function getData() {
const { can } = await accessControlProvider.can({
resource: "posts",
action: "list",
});
return {
can,
};
}
FAQ
How can I use my providers in the both server and client side?
Error: Functions cannot be passed directly to Client Components unless you explicitly expose it by marking it with "use server".
Error: It's not possible to invoke a client function from the server, it can only be rendered as a Component or passed to props of a Client Component.
In some cases, you may want to use your providers on both the server and client side. For example, you may want to use your data provider in your server components to fetch data even before the page is rendered but also you need to give the same data provider to your Refine components to fetch data in the client side.
Since Refine heavily uses React context and React state, It's needed to be a client component, and we can't pass functions into the Refine component unless we mark them as client
functions with "use client";
directive. After marking them as client
functions, you can't use them on the server side.
To solve this problem, you can create a separate file for your providers and mark them as client
or server
functions. Then, you can use the appropriate provider in your server and client components.
Dependencies:
Code Files
You can learn more about this on the Next.js documentation.
Can I use nested routes?
Yes, you can use nested routes in your app. Refine will match the routes depending on how you define the action paths in your resources. Additional parameters and nesting is supported. Refine will not limit you and your router in route configuration, all you need to do is to pass the appropriate path to the related resource and the action in the resources
array (This is also optional but recommended due to the features it provides).
You can use :param
syntax to define parameters in your routes.
How to make SSR work?
- App router
- Pages Router
You can use the methods provided from the dataProvider
to fetch data in your server components. To do this, you can use normal async function and use it in your server component.
To learn more about how to fetch data in server components, you can check the data fetching documentation of Next.js.
import dataProvider from "@refinedev/simple-rest";
const API_URL = "https://api.fake-rest.refine.dev";
export default async function ProductList() {
const { posts, total } = await getData();
return (
<div>
<h1>Posts ({total})</h1>
<hr />
{posts.map((post) => (
<div key={post.id}>
<h1>{post.title}</h1>
<p>{post.body}</p>
</div>
))}
</div>
);
}
async function getData() {
const response = await dataProvider(API_URL).getList({
resource: "posts",
pagination: { current: 1, pageSize: 10 },
});
return {
posts: response.data,
total: response.total,
};
}
You can always use the methods provided from the dataProvider
to fetch data in your pages. To do this, you can both use getServerSideProps
or getStaticProps
methods and pass the data to your page as a prop.
All you need to do is to pass the data as the initialData
to your data hooks using the queryOptions
prop.
import { useList } from "@refinedev/core";
import { dataProvider } from "src/providers";
type IPost = {
id: number;
title: string;
description: string;
};
export const getServerSideProps = async () => {
const { data } = await dataProvider.getList<IPost>("posts", {
pagination: {
page: 1,
perPage: 10,
},
});
return {
props: {
posts: data,
},
};
};
export default function Posts({ posts }: { posts: GetListResponse<IPost> }) {
const {
tableQuery: { data },
} = useTable<IPost>({
queryOptions: {
initialData: posts,
},
});
return <>{/* ... */}</>;
}
How to persist syncWithLocation
in SSR?
If syncWithLocation
is enabled, query parameters must be handled while doing SSR.
For example, search params can be like this:
- App router
- Pages Router
import dataProvider from "@refinedev/simple-rest";
import { type CrudFilters, type Pagination } from "@refinedev/core";
import { parseTableParams } from "@refinedev/nextjs-router";
const API_URL = "https://api.fake-rest.refine.dev";
type PostListProps = {
params?: Record<string, string>;
searchParams?: Record<string, string>;
};
export default async function PostList({ searchParams }: PostListProps) {
const tableParams = parseTableParams(searchParams);
const { posts, total } = await getData(tableParams);
return (
<div>
<h1>Posts ({total})</h1>
<hr />
{posts?.map((post) => (
<div key={post.id}>
<h1>{post.title}</h1>
<p>{post.body}</p>
</div>
))}
</div>
);
}
type GetDataParams = {
pagination?: Pagination;
filters?: CrudFilters;
};
async function getData(
params: GetDataParams = {
pagination: { current: 1, pageSize: 10 },
filters: [],
},
) {
const response = await dataProvider(API_URL).getList({
resource: "posts",
pagination: params.pagination,
filters: params.filters,
});
return {
posts: response.data,
total: response.total,
};
}
If syncWithLocation
is enabled, query parameters must be handled while doing SSR.
import { parseTableParams } from "@refinedev/nextjs-router/pages";
import dataProvider from "@refinedev/simple-rest";
const API_URL = "https://api.fake-rest.refine.dev";
export const getServerSideProps = ({ params, resolvedUrl }) => {
const { resource } = params;
const tableParams = parseTableParams(resolvedUrl?.split("?")[1] ?? "");
try {
const data = await dataProvider(API_URL).getList({
resource: resource as string,
...tableParams, // this includes `filters`, `sorters` and `pagination`
});
return {
props: {
initialData: data,
},
};
} catch (error) {
return {
props: {},
};
}
};
export default function MyListPage({ initialData }) {
return <>{/* ... */}</>;
}
parseTableParams
parses the query string and returns query parameters([refer here for their interfaces][interfaces]). They can be directly used for dataProvider
methods that accept them.
How to use multiple layouts?
You'll probably have a point where you need to use multiple layouts. For example, you may want to use a different layout for the login page. To achieve this, you can either use your Layout
wrappers in your pages or you can add extra properties to your page components to render the page with the specified layout.
Here's an example of how you can use additional properties to render different layouts.
- App router
- Pages Router
The App Router inside Next.js 13 introduced new file conventions to easily create pages, shared layouts, and templates. You can define a layout by default exporting a React component from a layout.js
file.
File structure should similar to this:
├── app
│ ├── posts
│ │ ├── page.tsx
│ │ └── layout.tsx
│ ├── login
│ │ ├── page.tsx
│ │ └── layout.tsx
│ ├── layout.tsx
│ ├── page.tsx
│
In the example above we have three layouts.
- app/layout.tsx - The default layout and wraps all the pages.
- app/posts/layout.tsx - The layout for the posts page.
- app/login/layout.tsx - The layout for the login page.
To learn more about this, you can check the Next.js documentation.
export default function Login() {
return (
<div>
<h1>Login</h1>
</div>
);
}
Login.layout = "auth";
export default function Posts() {
return <div>{/* ... */}</div>;
}
Posts.layout = "default";
import { AuthLayout, DefaultLayout } from "src/layouts";
const Layouts = {
auth: AuthLayout,
default: DefaultLayout,
};
export default function MyApp({ Component, pageProps }) {
const Layout = Layouts[Component.layout ?? "default"];
return (
<Refine
{/* ... */}
>
<Layout>
<Component {...pageProps} />
</Layout>
</Refine>
);
}
To comply with TypeScript types, we'll need to extend the NextPage
and AppProps
types accordingly to expect the layout
property in page components.
You can find out more about this at Next.js documentation for multiple layouts
We'll define the types in _app.tsx
file.
import type { NextPage } from "next";
import type { AppProps } from "next/app";
export type PageWithLayout<P = {}, IP = P> = NextPage<P, IP> & {
layout?: keyof typeof Layouts;
};
type AppPropsWithLayout = AppProps & {
Component: PageWithLayout;
};
// Then we'll change the type of `MyApp` components props to `AppPropsWithLayout`.
export default function MyApp({ Component, pageProps }: AppPropsWithLayout) {
/* ... */
}
Then in our pages, we can use the PageWithLayout
type when defining our page components.
import type { PageWithLayout } from "pages/_app";
const Login: PageWithLayout = () => {
return (
<div>
<h1>Login</h1>
</div>
);
};
Login.layout = "auth";
Handling 404s
In the earlier versions of Refine, if authProvider
was defined, we've redirected the users to the /login
route even with the 404s and 404 pages were only available to the authenticated users. Now, the routes are handled by the users, so you can handle the 404s however you like.
- App router
- Pages Router
Using the Next.js's not-found.js
If you want handle unmatched routes, you can create a not-found.tsx
file in your /app
directory and it will be used as the 404 page. For more information, you can check the Next.js documentation for not-found.js.
import { ErrorComponent } from "@refinedev/antd";
import { Suspense } from "react";
export default function NotFound() {
return (
<Suspense>
<ErrorComponent />
</Suspense>
);
}
Using a catch-all route
If you want to achieve the legacy behavior or want to have more control over the unhandled routes, you can use the catch-all route. For more information, you can check the Next.js documentation for catch-all route.
Client Side
"use client";
import { Authenticated } from "@refinedev/core";
export default function CatchAll() {
return (
// This will redirect the user if they're not authenticated depending on the response of `authProvider.check`.
<Authenticated>
<div>This page is not found.</div>
</Authenticated>
);
}
Server Side
import { redirect } from "next/navigation";
import { cookies } from "next/headers";
export default async function Layout({ children }: React.PropsWithChildren) {
const data = await getData();
if (!data.authenticated) {
return redirect("/login");
}
return <>{children}</>;
}
async function getData() {
const cookieStore = cookies();
const auth = cookieStore.get("auth");
return {
authenticated = !!auth,
};
}
Using the Next.js's 404 page
If you want to use the Next.js's 404 page, you can create a 404.tsx
file in your /pages
directory and it will be used as the 404 page. For more information, you can check the Next.js documentation for custom 404 page.
Using a catch-all route
If you want to achieve the legacy behavior or want to have more control over the unhandled routes, you can use the catch-all route. For more information, you can check the Next.js documentation for catch-all route.
You can use Refine's authentication hooks and the authProvider
to check if the user is authenticated or not and redirect them to the login page. This check can be done on the client-side or server-side.
Client Side
import { Authenticated } from "@refinedev/core";
export default function CatchAll() {
return (
// This will redirect the user if they're not authenticated depending on the response of `authProvider.check`.
<Authenticated>
<div>This page is not found.</div>
</Authenticated>
);
}
Server Side
import { authProvider } from "src/authProvider";
export const getServerSideProps = async (context) => {
const { authenticated, redirectTo } = await authProvider.check(context);
if (!authenticated && redirectTo) {
return {
redirect: {
destination: redirectTo,
permanent: false,
},
props: {},
};
}
return {
props: {},
};
};
export default function CatchAll() {
return <div>This page is not found.</div>;
}
RefineRoutes
Component
While this may work for the simple cases, it is not recommended to use this component. Defining your routes separately will give you more control over your routes and will allow you to use the full potential of your router.
This component can be used to render the matching route in your resources by using the resources
prop. It will only take effect if the action properties in the resource definitions are assigned to components or objects with component
property.
It will render the component for the matching route and pass it as a JSX.Element
to the children
function. You can use this to render your components in a single catch-all route. If there's no matching route undefined
will be passed to the children
function. In this case, you can render an error page or redirect the user to another page.
We'll define our resources in the <Refine>
component:
return (
<Refine
resources={[
{
name: "posts",
list: "/posts",
show: "/posts/show/:id",
},
{
name: "categories",
list: "/categories",
},
]}
>
{/* ... */}
</Refine>
);
Then, we'll create a catch-all route to render the matching route in our resources:
In `app/[[...refine]]/page.tsx`
If you're using appDir
in your Next.js project, you can create a catch-all route in the app
directory.
"use client";
import { RefineRoutes } from "@refinedev/nextjs-router";
export default function CatchAll() {
return (
<RefineRoutes>
{(matchingRoute) => {
if (matchingRoute) {
return { matchingRoute };
}
return <ErrorPage />;
}}
</RefineRoutes>
);
}
In `pages/[[...refine]].tsx`
If you're using the /pages
directory in your Next.js project, you can create a catch-all route in the pages
directory.
import { RefineRoutes } from "@refinedev/nextjs-router/pages";
import { ErrorPage } from "components/error";
export default function CatchAll() {
return (
<RefineRoutes>
{(matchingRoute) => {
if (matchingRoute) {
return { matchingRoute };
}
return <ErrorPage />;
}}
</RefineRoutes>
);
}
When components are used to define the resource actions, default paths will be used. You can override the default paths by assigning an object with component
and path
properties to the action properties.
Default paths are:
list
:/resources
create
:/resources/create
edit
:/resources/edit/:id
show
:/resources/show/:id
How to change the document title?
By default <DocumentTitleHandler/>
component will generate the document title based on current resource and action with the "Refine" suffix. You can customize the title generation process by providing a custom handler
function.
import {
BrowserRouter,
DocumentTitleHandler,
} from "@refinedev/react-router-v6";
import { Refine } from "@refinedev/core";
const App = () => {
return (
<BrowserRouter>
<Refine
/* ... */
>
{/* ... */}
<DocumentTitleHandler
handler={({ action, params, resource }) => {
const id = params?.id ?? "";
const actionPrefixMatcher = {
create: "Create new ",
clone: `#${id} Clone ${resource?.meta?.label}`,
edit: `#${id} Edit ${resource?.meta?.label}`,
show: `#${id} Show ${resource?.meta?.label}`,
list: `${resource?.meta?.label}`,
};
const suffix = " | <Company Name>";
const title = actionPrefixMatcher[action || "list"] + suffix;
return title;
}}
/>
</Refine>
</BrowserRouter>
);
};
Example (/app
)
npm create refine-app@latest -- --example with-nextjs
- Basic Usage
/app
Directory/pages
Directory- Additional Components
- NavigateToResource
- In
app/page.tsx
- In
pages/index.tsx
- Properties
- UnsavedChangesNotifier
- Properties
- parseTableParams
- DocumentTitleHandler
- Properties
- Hooks
- useDocumentTitle
- Authentication
- Client side
- Server Side
app
directorypages
directory- Access Control
- Client Side
- Server Side
app
directorypages
directory- FAQ
- How can I use my providers in the both server and client side?
- Can I use nested routes?
- How to make SSR work?
- How to persist
syncWithLocation
in SSR? - How to use multiple layouts?
- Handling 404s
RefineRoutes
Component- How to change the document title?
- Example (
/app
)