Per-Page Layouts in T3

Hey guys I was attempting to implement per page layouts on the t3 starter template as shown here: https://nextjs.org/docs/basic-features/layouts#with-typescript I kept running into Typescript issues. Any way I could go about this in a way I could make Typescript happy? There is a squiggly line on MyApp and the getLayout function argument in the return. Feel free to suggest a better approach for this.
// pages/_app.tsx
import type { AppProps } from "next/app";
import { type AppType } from "next/app";
import { type Session } from "next-auth";
import { SessionProvider } from "next-auth/react";

import { trpc } from "../utils/trpc";

import "../styles/globals.css";
import type { NextPage } from "next";
import type { ReactElement, ReactNode } from "react";


export type NextPageWithLayout<P = {}, IP = P> = NextPage<P, IP> & {
getLayout?: (page: ReactElement) => ReactNode;
};

type AppPropsWithLayout = AppProps & {
Component: NextPageWithLayout;
};

const MyApp: AppType<{ session: Session | null }> = ({
Component,
pageProps: { session, ...pageProps },
}: AppPropsWithLayout) => {
const getLayout = Component.getLayout || ((page: NextPage) => page);

return getLayout(
<SessionProvider session={session}>
<Component {...pageProps} />
</SessionProvider>
);
};

export default trpc.withTRPC(MyApp);
// pages/_app.tsx
import type { AppProps } from "next/app";
import { type AppType } from "next/app";
import { type Session } from "next-auth";
import { SessionProvider } from "next-auth/react";

import { trpc } from "../utils/trpc";

import "../styles/globals.css";
import type { NextPage } from "next";
import type { ReactElement, ReactNode } from "react";


export type NextPageWithLayout<P = {}, IP = P> = NextPage<P, IP> & {
getLayout?: (page: ReactElement) => ReactNode;
};

type AppPropsWithLayout = AppProps & {
Component: NextPageWithLayout;
};

const MyApp: AppType<{ session: Session | null }> = ({
Component,
pageProps: { session, ...pageProps },
}: AppPropsWithLayout) => {
const getLayout = Component.getLayout || ((page: NextPage) => page);

return getLayout(
<SessionProvider session={session}>
<Component {...pageProps} />
</SessionProvider>
);
};

export default trpc.withTRPC(MyApp);
// pages/index.tsx
import type { ReactElement } from "react";
import Layout from "../components/Layout/Layout";
import type { NextPageWithLayout } from "./_app";

const Home: NextPageWithLayout = () => {
return (
<>
<div>Hello</div>
</>
);
};

Home.getLayout = function getLayout(page: ReactElement) {
return <Layout>{page}</Layout>;
};

export default Home;
// pages/index.tsx
import type { ReactElement } from "react";
import Layout from "../components/Layout/Layout";
import type { NextPageWithLayout } from "./_app";

const Home: NextPageWithLayout = () => {
return (
<>
<div>Hello</div>
</>
);
};

Home.getLayout = function getLayout(page: ReactElement) {
return <Layout>{page}</Layout>;
};

export default Home;
Basic Features: Layouts | Next.js
Learn how to share components and state between Next.js pages with Layouts.
14 Replies
Unknown User
Unknown User3y ago
Message Not Public
Sign In & Join Server To View
Mik3y-F
Mik3y-FOP3y ago
Type '({ Component, pageProps: { session, ...pageProps }, }: AppPropsWithLayout) => string | number | boolean | ReactElement<any, string | JSXElementConstructor<any>> | ReactFragment | (ComponentClass<...> & { ...; }) | (FunctionComponent<...> & { ...; }) | null | undefined' is not assignable to type 'AppType<{ session: Session | null; }>'.
Type '({ Component, pageProps: { session, ...pageProps }, }: AppPropsWithLayout) => string | number | boolean | ReactElement<any, string | JSXElementConstructor<any>> | ReactFragment | (ComponentClass<...> & { ...; }) | (FunctionComponent<...> & { ...; }) | null | undefined' is not assignable to type 'FunctionComponent<AppPropsType<any, { session: Session | null; }>> & { getInitialProps?(context: AppContextType<NextRouter>): { ...; } | Promise<...>; }'.
Type '({ Component, pageProps: { session, ...pageProps }, }: AppPropsWithLayout) => string | number | boolean | ReactElement<any, string | JSXElementConstructor<any>> | ReactFragment | (ComponentClass<...> & { ...; }) | (FunctionComponent<...> & { ...; }) | null | undefined' is not assignable to type 'FunctionComponent<AppPropsType<any, { session: Session | null; }>>'.
Type 'string | number | boolean | ReactElement<any, string | JSXElementConstructor<any>> | ReactFragment | (ComponentClass<{}, any> & { ...; }) | (FunctionComponent<...> & { ...; }) | null | undefined' is not assignable to type 'ReactElement<any, any> | null'.
Type 'undefined' is not assignable to type 'ReactElement<any, any> | null'.ts(2322)
Type '({ Component, pageProps: { session, ...pageProps }, }: AppPropsWithLayout) => string | number | boolean | ReactElement<any, string | JSXElementConstructor<any>> | ReactFragment | (ComponentClass<...> & { ...; }) | (FunctionComponent<...> & { ...; }) | null | undefined' is not assignable to type 'AppType<{ session: Session | null; }>'.
Type '({ Component, pageProps: { session, ...pageProps }, }: AppPropsWithLayout) => string | number | boolean | ReactElement<any, string | JSXElementConstructor<any>> | ReactFragment | (ComponentClass<...> & { ...; }) | (FunctionComponent<...> & { ...; }) | null | undefined' is not assignable to type 'FunctionComponent<AppPropsType<any, { session: Session | null; }>> & { getInitialProps?(context: AppContextType<NextRouter>): { ...; } | Promise<...>; }'.
Type '({ Component, pageProps: { session, ...pageProps }, }: AppPropsWithLayout) => string | number | boolean | ReactElement<any, string | JSXElementConstructor<any>> | ReactFragment | (ComponentClass<...> & { ...; }) | (FunctionComponent<...> & { ...; }) | null | undefined' is not assignable to type 'FunctionComponent<AppPropsType<any, { session: Session | null; }>>'.
Type 'string | number | boolean | ReactElement<any, string | JSXElementConstructor<any>> | ReactFragment | (ComponentClass<{}, any> & { ...; }) | (FunctionComponent<...> & { ...; }) | null | undefined' is not assignable to type 'ReactElement<any, any> | null'.
Type 'undefined' is not assignable to type 'ReactElement<any, any> | null'.ts(2322)
The one above is for the MyApp declaration squiggly Line the one on the return getLayout call argument is this
Argument of type 'Element' is not assignable to parameter of type 'ReactElement<any, string | JSXElementConstructor<any>> & NextPage<{}, {}>'.
Type 'ReactElement<any, any>' is not assignable to type 'ReactElement<any, string | JSXElementConstructor<any>> & FunctionComponent<{}> & { getInitialProps?(context: NextPageContext): {} | Promise<...>; }'.
Type 'ReactElement<any, any>' is not assignable to type 'FunctionComponent<{}>'.
Type 'ReactElement<any, any>' provides no match for the signature '(props: {}, context?: any): ReactElement<any, any> | null'.ts(2345)
Argument of type 'Element' is not assignable to parameter of type 'ReactElement<any, string | JSXElementConstructor<any>> & NextPage<{}, {}>'.
Type 'ReactElement<any, any>' is not assignable to type 'ReactElement<any, string | JSXElementConstructor<any>> & FunctionComponent<{}> & { getInitialProps?(context: NextPageContext): {} | Promise<...>; }'.
Type 'ReactElement<any, any>' is not assignable to type 'FunctionComponent<{}>'.
Type 'ReactElement<any, any>' provides no match for the signature '(props: {}, context?: any): ReactElement<any, any> | null'.ts(2345)
Unknown User
Unknown User3y ago
Message Not Public
Sign In & Join Server To View
esponges
esponges3y ago
export type NextPageWithLayout<P = {}, IP = P> = NextPage<P, IP> & {
getLayout?: (page: ReactElement) => ReactNode;
};

type AppPropsWithLayout = AppProps & {
Component: NextPageWithLayout;
pageProps: {
session: Session | null;
}
};

const MyApp: AppType<{ session: Session | null }> = ({
Component,
pageProps: { session, ...pageProps },
}: AppPropsWithLayout) => {
const getLayout = Component.getLayout || ((page) => (<MainLayout>{page}</MainLayout>));
const layout = getLayout(<Component {...pageProps} />);

return (
<SessionProvider session={session}>
{layout}
<ReactQueryDevtools />
</SessionProvider>
);
};

export default trpc.withTRPC(MyApp);
export type NextPageWithLayout<P = {}, IP = P> = NextPage<P, IP> & {
getLayout?: (page: ReactElement) => ReactNode;
};

type AppPropsWithLayout = AppProps & {
Component: NextPageWithLayout;
pageProps: {
session: Session | null;
}
};

const MyApp: AppType<{ session: Session | null }> = ({
Component,
pageProps: { session, ...pageProps },
}: AppPropsWithLayout) => {
const getLayout = Component.getLayout || ((page) => (<MainLayout>{page}</MainLayout>));
const layout = getLayout(<Component {...pageProps} />);

return (
<SessionProvider session={session}>
{layout}
<ReactQueryDevtools />
</SessionProvider>
);
};

export default trpc.withTRPC(MyApp);
got it working like this I added a default MainLayout for all the routes that can be overridden by every Page Component with the getLayout like so:
Checkout.getLayout = function getLayout(page: ReactElement) {
return <ProtectedLayout>{page}</ProtectedLayout>
};
Checkout.getLayout = function getLayout(page: ReactElement) {
return <ProtectedLayout>{page}</ProtectedLayout>
};
Mik3y-F
Mik3y-FOP3y ago
Ahaaaaaa thanks Man works for me too ...
esponges
esponges3y ago
Awesome, today I was struggling with the same lol
Mik3y-F
Mik3y-FOP3y ago
Dope stuff ..... Thank you sooo much for the work around
esponges
esponges3y ago
sure, if you are curious about the full implementation here it's, good luck 👍 https://github.com/esponges/t3-ecommerce
Unknown User
Unknown User3y ago
Message Not Public
Sign In & Join Server To View
esponges
esponges3y ago
hmm I have checked and session is taking Session | null as types, the compiler took some secs to update it btw
Unknown User
Unknown User3y ago
Message Not Public
Sign In & Join Server To View
jnsdrssn
jnsdrssn3y ago
This seems to work, no guarantee
Unknown User
Unknown User3y ago
Message Not Public
Sign In & Join Server To View
ziN
ziN3y ago
@Mik3y-F What's the benefits of using this .getLayout approach compare to wrapping each page content with some layout component?
Want results from more Discord servers?
Add your server