Skip to main content
Version: 4.xx.xx

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 i @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 create refine-app@latest -- -o refine-nextjs my-refine-nextjs-app

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 posts
  • app/posts/show/[id]/page.tsx - Detail page for posts
  • app/categories/page.tsx - List page for categories
  • app/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:

app/layout.tsx
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.

Good to know:
  • 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.

  • 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 as client 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:

app/posts/page.tsx
"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>
);
}
pages/posts/show/[id]/page.tsx
"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>
);
}
pages/categories/page.tsx
"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>
);
}
pages/categories/show/[id]/page.tsx
"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.

Implementation Tips:

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.

app/page.tsx
"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 posts
  • pages/posts/show/[id].tsx - Detail page for posts
  • pages/categories/index.tsx - List page for categories
  • pages/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:

_app.tsx
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>
);
}
Good to know:

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:

pages/posts/index.tsx
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>
);
}
pages/posts/show/[id].tsx
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>
);
}
pages/categories/index.tsx
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>
);
}
pages/categories/show/[id].tsx
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.

Implementation Tips:

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.

pages/index.tsx
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.

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

app/page.tsx
"use client";

import { NavigateToResource } from "@refinedev/nextjs-router";

export default function IndexPage() {
return <NavigateToResource />;
}

In pages/index.tsx

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 the next/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.

_app.tsx
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>
);
}
INFORMATION

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 the id 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;

Implementation Tips:

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

Implementation Tips:

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 i nookies

We will set/destroy cookies in the login, logout and check functions of our AuthProvider.

src/providers/auth-provider.ts
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.

app/blog-posts/layout.ts
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...

pages/posts/index.tsx
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..

Implementation Tips:

For page level access control, server-side approach is recommended.

First, let's build our AccessControlProvider

src/acccessControlProvider.ts
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.

app/posts/page.tsx
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.

pages/posts/index.tsx
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?

Possible Errors:
  • 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.

import { Refine } from "@refinedev/core";
import routerProvider from "@refinedev/nextjs-router";
import React, { Suspense } from "react";

import { authProviderClient } from "@providers/auth-provider/auth-provider";
import { dataProviderClient } from "@providers/data-provider/data-provider.client";
import { accessControlProviderClient } from "@providers/access-control/access-control.client";

export default function RootLayout({
    children,
}: Readonly<{
    children: React.ReactNode;
}>) {
    return (
        <html lang="en">
            <body>
                <Suspense>
                    <Refine
                        routerProvider={routerProvider}
                        dataProvider={dataProviderClient}
                        authProvider={authProviderClient}
                        accessControlProvider={accessControlProviderClient}
                        resources={[
                            {
                                name: "blog_posts",
                                list: "/blog-posts",
                                create: "/blog-posts/create",
                                edit: "/blog-posts/edit/:id",
                                show: "/blog-posts/show/:id",
                                meta: {
                                    canDelete: true,
                                },
                            },
                        ]}
                    >
                        {children}
                    </Refine>
                </Suspense>
            </body>
        </html>
    );
}

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?

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.

app/posts/page.tsx
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,
};
}

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/posts/page.tsx
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,
};
}

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.

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.

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.

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.

app/not-found.tsx
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

app/[[...slug]]/page.tsx
"use client";