reactjstypescriptnext.jshigher-order-components

Persistent layout in Next.js with TypeScript and HOC


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


Solution

  • 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.