Skip to main content
Version: 2.xx.xx

SSR-Next.js

refine can be used with Next.js to SSR your pages. It doesn't get in the way and follows Next.js conventions and also provides helper modules when necessary.

Setup

nextjs-router package provided by refine must be used for the routerProvider

npm i @pankod/refine @pankod/refine-nextjs-router
tip

We recommend superplate to initialize your refine projects. It configures the project according to your needs including SSR with Next.js.

Usage

<Refine> must wrap your pages in a custom App component. This way your pages are integrated to refine.

pages/_app.tsx
import { AppProps } from "next/app";

import { Refine } from "@pankod/refine";
import dataProvider from "@pankod/refine-simple-rest";
import routerProvider from "@pankod/refine-nextjs-router";

const API_URL = "https://api.fake-rest.refine.dev";

function MyApp({ Component, pageProps }: AppProps): JSX.Element {
return (
<Refine
routerProvider={routerProvider}
dataProvider={dataProvider(API_URL)}
>
<Component {...pageProps} />
</Refine>
);
}

export default MyApp;

Custom Page

Let's say we want to show a list of users in /users. After creating users.tsx under pages in your Nextjs app, we can use the useTable hook to list the users in a table:

pages/users.tsx
import {
useTable,
List,
Table,
LayoutWrapper,
} from "@pankod/refine";

const API_URL = "https://api.fake-rest.refine.dev";
export const UserList: React.FC = () => {
const { tableProps } = useTable<IPost>({
resource: "users"
});

return (
<LayoutWrapper>
<List title="Users">
<Table {...tableProps} rowKey="id">
<Table.Column dataIndex="id" title="ID" sorter />
<Table.Column dataIndex="firstName" title="Name" />
</Table>
</List>
</LayoutWrapper>
);
};
interface IPost {
id: string;
firstName: string;
}

export default UserList;
important

Notice how we passed resource prop to useTable. This is necessary since for useTable to be able to get resource name from route, it needs to be a route parameter in a dynamic route. Refer here where standard CRUD pages can be built with dynamic routing.

important

We also used <LayoutWrapper> to show the page in the layout provided to <Refine>. This is deliberately opt-in to provide flexibility. If you're building a standard CRUD page layout can be baked in automatically.

SSR

refine uses react-query in its hooks for data management. Following react-query's guide, SSR can be achieved like this:

pages/users.tsx
import { GetServerSideProps } from "next";
import dataProvider from "@pankod/refine-simple-rest";
import {
useTable,
List,
Table,
LayoutWrapper,
GetListResponse,
} from "@pankod/refine";

const API_URL = "https://api.fake-rest.refine.dev";
export const UserList: React.FC<{ users: GetListResponse<IPost> }> = ({
users
}) => {
const { tableProps } = useTable<IPost>({
resource: "users",
queryOptions: {
initialData: users,
},
});

return (
<LayoutWrapper>
<List title="Users">
<Table {...tableProps} rowKey="id">
<Table.Column dataIndex="id" title="ID" sorter />
<Table.Column dataIndex="firstName" title="Name" />
</Table>
</List>
</LayoutWrapper>
);
};

export const getServerSideProps: GetServerSideProps = async (context) => {
const data = await dataProvider(API_URL).getList({
resource: "users",
});

return {
props: { users: data },
};
};

interface IPost {
id: string;
firstName: string;
}

export default UserList;

We use the getList method from our dataProvider to fetch users data and pass through props as conventionally done in Next.js. Then users data is available in the props of our /users page. useTable can take options for underlying react-query queries with queryOptions. Passing users data to its initialData loads the data on server side.

tip

We used getList from dataProvider but data can be fetched in any way you desire.

Standard CRUD Page

nextjs-router package provides NextRouteComponent for pages with the dynamic route /[resource]/[action]/[id] and root /. Simply export the component from the page and add a data fetching function

pages/[resource]/index.tsx
export { NextRouteComponent as default } from "@pankod/refine-nextjs-router";

export const getServerSideProps: GetServerSideProps = async () => {
return { props: {} };
};
warning

NextRouteComponent doesn't support automatic static optimization currently, since it requires route parameters thus a data fetching function must be defined.

NextRouteComponent can be used in the following pages:

  • pages/[resource].tsx
  • pages/[resource]/[action].tsx
  • pages/[resource]/[action]/[id].tsx
  • pages/index.tsx

NextRouteComponent will use route parameters resource and action and render the associated component defined in resources.

  • list component will be rendered for /[resource] route
  • create, edit and show will be rendered for /[resource]/[action] and /[resource]/[action]/[id] routes
  • For the root / route, it will render DashboardPage if it's defined and if not will navigate to the first resource in resources.
important

NextRouteComponent will wrap the page with Layout provided to <Refine>

SSR

NextRouteComponent accepts a initialData prop for SSR data.

type NextRouteComponentProps = {
initialData?: any;
};

initialData must be passed as props from getServerSideProps. NextRouteComponent will pass this data as initialData to the list, create, edit and show components.

For example, for a list component that will be rendered for /[resource], the page can use SSR like this:

pages/[resource]/index.tsx
export { NextRouteComponent as default } from "@pankod/refine-nextjs-router";
import dataProvider from "@pankod/refine-simple-rest";

import { GetServerSideProps } from "next";

const API_URL = "https://api.fake-rest.refine.dev";

export const getServerSideProps: GetServerSideProps = async (context) => {

const { query } = context;

try {
const data = await dataProvider(API_URL).getList({
resource: query["resource"] as string,
});

return {
props: {
initialData: data,
},
};
} catch (error) {
return { props: {} };
}
};

And in the list component for a resource e.g. "posts":

src/components/posts/list.tsx
import {
useTable,
List,
Table,
GetListResponse,
} from "@pankod/refine";
import type { IResourceComponentsProps } from "@pankod/refine";

export const PostList: React.FC<
IResourceComponentsProps<GetListResponse<IPost>>
> = ({ initialData }) => {
const { tableProps } = useTable<IPost>({
queryOptions: {
initialData,
},
});

return (
<List>
<Table {...tableProps} rowKey="id">
<Table.Column dataIndex="id" title="ID" />
<Table.Column dataIndex="status" title="Status" />
</Table>
</List>
);
};

interface IPost {
id: string;
firstName: string;
}

Server Side Authentication

nextjs-router package provides checkAuthentication to easily handle server side authentication.

pages/[resource]/index.tsx
export { NextRouteComponent as default } from "@pankod/refine-nextjs-router";
import { checkAuthentication } from "@pankod/refine-nextjs-router";

import { GetServerSideProps } from "next";

import {authProvider} from "../../src/authProvider";

const API_URL = "https://api.fake-rest.refine.dev";

export const getServerSideProps: GetServerSideProps = async (context) => {

const { isAuthenticated, ...props } = await checkAuthentication(
authProvider,
context,
);

if (!isAuthenticated) {
return props;
}

return {
props: {},
};
};

checkAuthentication expects your authProvider and getServerSideProps's context. It uses the checkAuth from the authProvider to check for authentication and returns isAuthenticated accordingly. It also returns a redirect object to handle unauthenticated case. It redirects to /login while keeping the original route to be navigated to after successful login.

syncWithLocation and Query Parameters in SSR

If syncWithLocation is enabled, query parameters must be handled while doing SSR.

pages/users.tsx
import { GetServerSideProps } from "next";
import { parseTableParamsFromQuery } from "@pankod/refine";
import dataProvider from "@pankod/refine-simple-rest";

const API_URL = "https://api.fake-rest.refine.dev";

export const getServerSideProps: GetServerSideProps = async (context) => {

const { parsedCurrent, parsedPageSize, parsedSorter, parsedFilters } =
parseTableParamsFromQuery(context.query);
const data = await dataProvider(API_URL).getList({
resource: "users",
filters: parsedFilters,
pagination: {
current: parsedCurrent || 1,
pageSize: parsedPageSize || 10,
},
sort: parsedSorter,
});

return {
props: { users: data },
};
};

parseTableParams parses the query string and returns query parameters(refer here for their interfaces). They can be directly used for dataProvider methods that accepts them.

Live Codesandbox Example