44
Next.js Per-Page Layouts and TypeScript
Next.js allows developers to set up dynamic layouts per-page. Benefits and details of this approach can be read here. However, doing what is described there will generate some issues when we use TypeScript in strict mode.
Example code from the official documentation:
// pages/_app.tsx
export default function MyApp({ Component, pageProps }) {
// Use the layout defined at the page level, if available
const getLayout = Component.getLayout || ((page) => page)
return getLayout(<Component {...pageProps} />)
}
Generates following errors:
Parameter 'page' implicitly has an 'any' type.
Property 'getLayout' does not exist on type 'NextComponentType<NextPageContext, any, {}>'.
Property 'getLayout' does not exist on type 'ComponentClass<{}, any> & { getInitialProps?(context: NextPageContext): any; }'
The first one we can easily fix, by importing a proper type for the page param:
import { ReactNode } from 'react';
Let's use it in our code:
// pages/_app.tsx
export default function MyApp({ Component, pageProps }) {
// Use the layout defined at the page level, if available
const getLayout = Component.getLayout || ((page: ReactNode) => page)
return getLayout(<Component {...pageProps} />)
}
Great! The first error is gone.
The second one is more complicated. What happens is that original type for Component
doesn't include getLayout
function. We need to declare a new types. Let's create a next.d.ts
file somewhere in the project, with the following content:
// next.d.ts
import type {
NextComponentType,
NextPageContext,
NextLayoutComponentType,
} from 'next';
import type { AppProps } from 'next/app';
declare module 'next' {
type NextLayoutComponentType<P = {}> = NextComponentType<
NextPageContext,
any,
P
> & {
getLayout?: (page: ReactNode) => ReactNode;
};
}
declare module 'next/app' {
type AppLayoutProps<P = {}> = AppProps & {
Component: NextLayoutComponentType;
};
}
It creates new types NextLayoutComponentType
and AppLayoutProps
that we can use in place of original types. Our initial code will need to be changed to this:
// pages/_app.tsx
import { AppContext, AppInitialProps, AppLayoutProps } from 'next/app';
import type { NextComponentType } from 'next';
import { ReactNode } from 'react';
const MyApp: NextComponentType<AppContext, AppInitialProps, AppLayoutProps> = ({
Component,
pageProps,
}: AppLayoutProps) => {
const getLayout = Component.getLayout || ((page: ReactNode) => page);
return getLayout(<Component {...pageProps} />);
};
export default MyApp;
Please note that we are using the custom type we've created - AppLayoutProps
. It includes the other custom type for Component
that now contains getLayout
function.
This solution was based on ippo012/nextjs-starter project, in which author used a very similar approach.
44