echobind / bisonapp

A Full Stack Jamstack in-a-box brought to you by Echobind
MIT License
590 stars 28 forks source link

Persistent layouts by default #242

Open mcavaliere opened 2 years ago

mcavaliere commented 2 years ago

To keep page transitions snappy, we should be using page layout components and static getLayout methods. This allows us to have centralized header/footer content that doesn't get re-rendered when going from one page to another.

To see them in action, navigate back and forth from this article index page and any of the articles.

This article I wrote, Next.js Page Layouts and Dynamic Content shows how to inject dynamic page-specific content into the layouts and still have them equally snappy.

philipj93 commented 2 years ago

I really like this method of assigning layout functions to the page modules as a property. This worked pretty well in a JS app but it got kind of tricky for TS apps. Blitz.js does something pretty similar for their pages and layouts so I looked at their source to figure out how to make this technique work in a Next.js app with TypeScript. You can write a types/index.ts file and add:

import type { AppProps as NextAppProps } from 'next/app'
import type {
  NextComponentType as DefaultNextComponentType,
  NextPage as DefaultNextPage,
  NextPageContext,
} from 'next'

export type GetLayoutFunction = (component: JSX.Element) => JSX.Element

export declare type NextComponentType<
  C = NextPageContext,
  IP = Record<string, unknown>,
  P = Record<string, unknown>,
> = DefaultNextComponentType<C, IP, P>

export interface AppProps<P = Record<string, unknown>> extends NextAppProps<P> {
  Component: NextComponentType<NextPageContext, any, P> & {
    getLayout?: GetLayoutFunction
  }
}

export declare type NextPage<P = Record<string, unknown>, IP = P> = DefaultNextPage<P, IP> & {
  getLayout?: GetLayoutFunction
}

In _app.tsx import the AppProps type from the custom type definitions:

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

// New type import
import type { AppProps } from 'types'

function MyApp({ Component, pageProps }: AppProps) {
  const getLayout = Component.getLayout || ((page) => page)

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

export default MyApp

Then in a page files use the custom NextPage definition:

// Old type import
// import type { NextPage } from 'next'

// New type import
import type { NextPage } from '../types'

const HomePage: NextPage = () => {
  ...

Then the type errors are gone 😅