8

I want to add a persistent layout to certain pages of my Next.js application. I found this article explaining a couple ways on how someone could do this. It seems pretty straightforward, however I have encountered the following two problems when using the recommended way of doing it:

  1. I am using TypeScript and am not sure how to type it. For example, I have the following, which is working, but I obviously don't like using as any:
const getLayout =
    (Component as any).getLayout ||
    ((page: NextPage) => <SiteLayout children={page} />);
  1. I am using Apollo and so I am using a withApollo HOC (from here) for certain pages. Using this causes Component.getLayout to always be undefined. I don't have a good enough understanding of what is going on to know why this is happening (I can guess), so it's difficult to solve this by myself.

Since asking this question they have added a good example to their documentation

3
  • 1
    Did you ever find a better way to do this than using as any? Commented Sep 23, 2020 at 0:20
  • I did not @knite Commented Sep 24, 2020 at 7:31
  • for typescript next js team solve the problem you can read this article for more info nextjs.org/docs/basic-features/layouts#with-typescript Commented Jan 23, 2022 at 11:52

5 Answers 5

30

I have the similar problem and this is how I solved it for my project.

Create a types/page.d.ts type definition:

import { NextPage } from 'next'
import { ComponentType, ReactElement, ReactNode } from 'react'

export type Page<P = {}> = NextPage<P> & {
  // You can disable whichever you don't need
  getLayout?: (page: ReactElement) => ReactNode
  layout?: ComponentType
}

In your _app.tsx file,

import type { AppProps } from 'next/app'
import { Fragment } from 'react'
import type { Page } from '../types/page'

// this should give a better typing
type Props = AppProps & {
  Component: Page
}
const MyApp = ({ Component, pageProps }: Props) => {
  // adjust accordingly if you disabled a layout rendering option
  const getLayout = Component.getLayout ?? (page => page)
  const Layout = Component.layout ?? Fragment

  return (
    <Layout>
      {getLayout(<Component {...pageProps} />)}
    </Layout>
  )

  // or swap the layout rendering priority
  // return getLayout(<Layout><Component {...pageProps} /></Layout>)
}

export default MyApp

The above is just a sample implementation best suited for my use-case, you can switch the type in types/page.d.ts to fit your needs.

Sign up to request clarification or add additional context in comments.

1 Comment

This works for me like a charm! You sir, saved my plant based bacon, and I'll be eternally grateful for that!
6

You can extend Next type definitions by adding getLayout property to AppProps.Component.

next.d.ts (as of Next 11.1.0):

import type { CompletePrivateRouteInfo } from 'next/dist/shared/lib/router/router';
import type { Router } from 'next/dist/client/router';

declare module 'next/app' {
  export declare type AppProps = Pick<CompletePrivateRouteInfo, 'Component' | 'err'> & {
    router: Router;
  } & Record<string, any> & {
    Component: {
      getLayout?: (page: JSX.Element) => JSX.Element;
    }
  }
}

pages/_app.tsx:

import type { AppProps } from 'next/app';

const NextApp = ({ Component, pageProps }: AppProps) => {
  // Typescript does not complain about unknown property
  const getLayout = Component.getLayout || ((page) => page);
  // snip
}

If you want to have even more type-safety, you can define custom NextPageWithLayout type which will assert that your component has getLayout property set.

next.d.ts (as of Next 11.1.0):

import type { NextPage } from 'next';
import type { NextComponentType } from 'next/dist/next-server/lib/utils';

declare module 'next' {
  export declare type NextPageWithLayout<P = {}, IP = P> = NextPage<P, IP> & {
    getLayout: (component: NextComponentType) => JSX.Element;
  };
}

pages/example-page/index.tsx:

import type { NextPageWithLayout } from 'next';

const ExamplePage: NextPageWithLayout<ExamplePageProps> = () => {
  // snip
}

// If you won't define `getLayout` property on this component, TypeScript will complain
ExamplePage.getLayout = (page) => {
  // snip
}

Note that while extending Next type definitions you have to provide exact type/interface signature (together with matching generics), otherwise declaration merging won't work. This unfortunately means adjusting type definitions if library changes API.

2 Comments

In Next 12.1.6, NextComponentType is located on this path: next/dist/shared/lib/utils
@tymzap <P = {}... gives a type error. Should it be <P = object | undefined... instead?
0
  1. I believe next.js provides a Component prop as a part of AppProps. This example should have what you are looking for.
  2. You may need to assign getLayout after creating the component wrapped with withApollo:
import CustomLayout = CustomLayout;
const MyPage = () => (...);

const MyPageWithApollo = withApollo(MyPage);
MyPageWithApollo.Layout = CustomLayout;
export default MyPageWithApollo;

Additionally, next.js has two examples you can use to achieve exactly this (not written in typescript though):

  1. Dynamic App Layout
  2. With Apollo

1 Comment

1. I know it does, but Component itself does not have a getLayout function, so you need to change its type to make it work with TypeScript. Your example unfortunately does not help in the slightest. 2. Same problem since it's TypeScript, you can't just do MyPageWithApollo.Layout since Layout does not exist on the type NextPage.
0
// if no Layout is defined in page, use this
const Default: FC = ({children}) => <>{children}</>

function MyApp({Component, pageProps}: AppProps & {Component: {Layout: FC}}) {
  // this will make layput flexible. 
  // Layout is extracted from the page
  const Layout = Component.Layout ?? Default
  return (
      <Layout>
        <Component {...pageProps} />
      </Layout>
  )
}
export default MyApp

Let's say you want to use the Layout in "Home",

import { Layout } from "@components"

export default function Home({
     ...
}
Home.Layout = Layout

1 Comment

I've already accepted an answer and it is now part of their documentation, so this answer is not really providing anything useful
0

Just do that! No panic, life is easy.

import { PropsWithChildren } from "react";
    
export default function Layout({ children }: PropsWithChildren) {
    return <main>{children}</main>
}

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.