Next.js Per-Page Layouts and TypeScript
Tags: nextjs, typescript, javascript, webdev
Published: 2021-07-25 09:36:24

Next.js allows developers to set up dynamic layouts per-page. Benefits and details of this approach can be read [here](https://nextjs.org/docs/basic-features/layouts#per-page-layouts). 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:

```typescript
// 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:

```typescript
// 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:

```typescript
// 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:

```typescript
// 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](https://github.com/ippo012/nextjs-starter) project, in which author used a very similar approach.