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
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.
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:
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;
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.
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:
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.
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
export { NextRouteComponent as default } from "@pankod/refine-nextjs-router";
export const getServerSideProps: GetServerSideProps = async () => {
return { props: {} };
};
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]
routecreate
,edit
andshow
will be rendered for/[resource]/[action]
and/[resource]/[action]/[id]
routes- For the root
/
route, it will renderDashboardPage
if it's defined and if not will navigate to the first resource inresources
.
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:
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":
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.
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.
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.