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.
What's wrong
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; }'
Fix the first error
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.
Fix the second error
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.
Top comments (5)
Nice! Just a few days ago I faced this issue and solved it by modifying
_app.tsx
in the following way:Any drawbacks to this approach against the one you propose?
Thanks for sharing!
That's just brilliant! Thanks for sharing.
I see some generics missing though — at least from
next@11.1.0
. I added them and it ended up being this for me:I added some generics based on your code.
Amazing works. This is a very useful resource
This works for sure, but I'd like to have more explanation of How does this works. As a Junior developer it's hard to understand :D and I don't want to take information without knowing answer on the question ''Why"