Layout management - T3 Stack - Next.js v13

Im struggling with layout usage. Can't fugure out how to implement it well using the create T3 app starting point. Basically im creating a admin section that will have lets say 4 sub sections ( ...url.../admin/user, .../settings, .../dashboard, etc) and the subsections will be in the content of an admin layout that should persist states like active section but I couldn't find a workaround for this. On React.js i used to use react router dom and just setting the Outlet as children of the layout and thats all. As i read in next js docs, v13 beta has a better management on layouts but idk how to migrate from the t3 stack template to that struture to get what im looking for. Has anyone faced the same difficulty ?
13 Replies
stimw
stimw2y ago
not sure, have you known the getLayout function?
Benja
BenjaOP2y ago
I saw this approach but I dont understand completly how this fits with the folder routing. For example: I have this foldering src/pages/admin -- this folder will have all the admin sections I guess like "settings.tsx" so when accesing to "url.com/admin/settings will show that component and if I set the layout there when moving to other section like "url.com/admin/table" wil render the other component and layout again without persistance aka rerendering layout, isn't it ? Updating this issue: I implemented the getLayout approach and it worked well. Layout doesn't re-render and working great. A bit unconfortable to set manually the layout for all leafs on the directory files but not a big deal. Idk if it will be easy to migrate to further versions with new layout management.
chrisstian
chrisstian2y ago
@Benja can you share how to you implemented that? please
Benja
BenjaOP2y ago
Sure!! I will share you my files so adapt them to your structure and usage 1 - Config your _app to use the component layout
/* eslint-disable @typescript-eslint/ban-ts-comment */
import { type AppType } from 'next/app';
import { type Session } from 'next-auth';
import { SessionProvider } from 'next-auth/react';
import { ChakraProvider } from '@chakra-ui/react';
import { api } from '~/utils/api';
import { appTheme } from '~/theme/theme';

import '~/styles/globals.css';
import Head from 'next/head';

const MyApp: AppType<{ session: Session | null }> = ({
Component,
pageProps: { session, ...pageProps },
}) => {
// Create this method to get component layout
const getLayout =
//@ts-ignore
(Component?.getLayout as unknown) ?? ((page: unknown) => page);

return (
<ChakraProvider theme={appTheme} resetCSS>
<SessionProvider session={session}>
{
// In my case this line throws an error but nothing to worry just ignore it and
//call the created method
//@ts-ignore
getLayout(<Component {...pageProps} />)
}
</SessionProvider>
</ChakraProvider>
);
};
export default api.withTRPC(MyApp);
/* eslint-disable @typescript-eslint/ban-ts-comment */
import { type AppType } from 'next/app';
import { type Session } from 'next-auth';
import { SessionProvider } from 'next-auth/react';
import { ChakraProvider } from '@chakra-ui/react';
import { api } from '~/utils/api';
import { appTheme } from '~/theme/theme';

import '~/styles/globals.css';
import Head from 'next/head';

const MyApp: AppType<{ session: Session | null }> = ({
Component,
pageProps: { session, ...pageProps },
}) => {
// Create this method to get component layout
const getLayout =
//@ts-ignore
(Component?.getLayout as unknown) ?? ((page: unknown) => page);

return (
<ChakraProvider theme={appTheme} resetCSS>
<SessionProvider session={session}>
{
// In my case this line throws an error but nothing to worry just ignore it and
//call the created method
//@ts-ignore
getLayout(<Component {...pageProps} />)
}
</SessionProvider>
</ChakraProvider>
);
};
export default api.withTRPC(MyApp);
2 - Create your layout! In my case I created the folder layouts at src/layouts with a custom layout, but here I give you a basic one so you can try it out
import React, { ReactNode } from 'react';
interface AdminLayoutProps {
children: ReactNode;
}
const AdminLayout = ({ children }: AdminLayoutProps) => {
return (
<div style={{ display: 'flex', minHeight: '100vh' }}>
<nav
style={{
backgroundColor: '#1E293B',
color: 'white',
minWidth: '16rem',
padding: '2rem',
}}
>
{/* Sidebar content */}
</nav>
<main style={{ flex: 1, padding: '2rem' }}>
{/* Main content */}
{children}
</main>
</div>
);
};
export default AdminLayout;
import React, { ReactNode } from 'react';
interface AdminLayoutProps {
children: ReactNode;
}
const AdminLayout = ({ children }: AdminLayoutProps) => {
return (
<div style={{ display: 'flex', minHeight: '100vh' }}>
<nav
style={{
backgroundColor: '#1E293B',
color: 'white',
minWidth: '16rem',
padding: '2rem',
}}
>
{/* Sidebar content */}
</nav>
<main style={{ flex: 1, padding: '2rem' }}>
{/* Main content */}
{children}
</main>
</div>
);
};
export default AdminLayout;
3 - Use it on your dynamic routes! Well here I made the folder src/pages/admin/[id] so I can use dynamic ids of business on the admin section line http://myurl.com/admin/123456/dashboard or /table or /users. So for this I created in the folder src/pages/admin/[id] every section file like dashboard.tsx, table.tsx, users.tsx so it gets on the routing 4 - Section config for layout usage!! This is the important part and what I said it was a bit unconfortable: setting the layout on every section manually. Here is one of the sections with the layout setting:
import React from 'react'
import AdminDashboard from '~/layouts/AdminLayout';

const UsersSection = () => {
return (
<div>UsersSection</div>
)
}

UsersSection.getLayout = (page: any) => (
<AdminDashboard>{page}</AdminDashboard>
);

export default UsersSection
import React from 'react'
import AdminDashboard from '~/layouts/AdminLayout';

const UsersSection = () => {
return (
<div>UsersSection</div>
)
}

UsersSection.getLayout = (page: any) => (
<AdminDashboard>{page}</AdminDashboard>
);

export default UsersSection
5 - And thats all !! Maybe if you are doing similar to my project you would like to set a default route to urls like http://myurl.com/admin/123456 because if this folder ( src/pages/admin/[id] ) doesn't have an index.tsx file it will not respond as you would expect. So I made a config at next.config.mjs to get the flow i wanted like setting the /dashboard section as default when accessing http://myurl.com/admin/[id] directly, here is my approach:
/** @type {import("next").NextConfig} */
const config = {
reactStrictMode: false,

/**
* If you have `experimental: { appDir: true }` set, then you must comment the below `i18n` config
* out.
*
* @see https://github.com/vercel/next.js/issues/41980
*/
i18n: {
locales: ['en'],
defaultLocale: 'en',
},
redirects: async () => {
return [
{
source: '/admin',
destination: '/',
permanent: true,
},
{
source: '/admin/:id',
destination: '/admin/:id/dashboard',
permanent: true,
},
];
},
};
export default config;
/** @type {import("next").NextConfig} */
const config = {
reactStrictMode: false,

/**
* If you have `experimental: { appDir: true }` set, then you must comment the below `i18n` config
* out.
*
* @see https://github.com/vercel/next.js/issues/41980
*/
i18n: {
locales: ['en'],
defaultLocale: 'en',
},
redirects: async () => {
return [
{
source: '/admin',
destination: '/',
permanent: true,
},
{
source: '/admin/:id',
destination: '/admin/:id/dashboard',
permanent: true,
},
];
},
};
export default config;
Hope this guide helps you !!
chrisstian
chrisstian2y ago
Thanks you!!!!! Bro what's ur github? I'll give you a star!
sarahmiller321
where does the getLayout pattern come from? @Benja
Benja
BenjaOP2y ago
GitHub
BenjaOliva - Overview
Developer at GlobalThink Technology. BenjaOliva has 15 repositories available. Follow their code on GitHub.
Benja
BenjaOP2y ago
Well is not like a pattern itself. Functions are like object variables and you are setting the layout at Component.getLayout and as you are setting this layouts on you components, they are rendering at _app flow to get layout and set it's corresponding children. I guess that's you are asking for
Filip
Filip2y ago
For anyone who wants to get the layout working with types, I've cobbled together the following through Benja's superb advice above, Nextjs's docs and some other searches. I'm fairly new to TS, take this with a grain of salt but it appears to be working. Using the pages router, in _app.tsx, I have the following:
import { type Session } from "next-auth";
import { type NextPage } from "next";
import { type AppProps } from "next/app";
import { type ReactElement, ReactNode } from "react";
import { SessionProvider } from "next-auth/react";
import { api } from "~/utils/api";

import "~/styles/globals.css";
import "~/styles/Calendar.css";
import "~/styles/Data-title.css";

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

type AppPropsWithLayout<P = {}> = AppProps & {
Component: NextPageWithLayout<P>;
};

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

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

export default api.withTRPC(MyApp);
import { type Session } from "next-auth";
import { type NextPage } from "next";
import { type AppProps } from "next/app";
import { type ReactElement, ReactNode } from "react";
import { SessionProvider } from "next-auth/react";
import { api } from "~/utils/api";

import "~/styles/globals.css";
import "~/styles/Calendar.css";
import "~/styles/Data-title.css";

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

type AppPropsWithLayout<P = {}> = AppProps & {
Component: NextPageWithLayout<P>;
};

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

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

export default api.withTRPC(MyApp);
In my Layout component, I have:
import React, { ReactNode } from 'react';
import { NavbarMain } from './NavbarMain';
import { Footer } from './Footer';

interface LayoutProps {
children: ReactNode;
}

export default function Layout({ children }: LayoutProps) {
return (
<div>
<NavbarMain />
<main>{children}</main>
<Footer />
</div>
);
}
import React, { ReactNode } from 'react';
import { NavbarMain } from './NavbarMain';
import { Footer } from './Footer';

interface LayoutProps {
children: ReactNode;
}

export default function Layout({ children }: LayoutProps) {
return (
<div>
<NavbarMain />
<main>{children}</main>
<Footer />
</div>
);
}
Finally, in index.tsx, I have (which I reckon needs to be replicated for each page that's going to use the layout).
import Head from "next/head";
import { type ReactElement } from 'react';
import { signIn, signOut, useSession } from "next-auth/react";
import { api } from "~/utils/api";
import Layout from "~/components/Layout";
import { type NextPageWithLayout } from "./_app";

const Home: NextPageWithLayout = () => {
const hello = api.example.hello.useQuery({ text: "from tRPC" }, {refetchOnWindowFocus: false,});

return (
<>
<Head>
...omitted for brevity...
</Head>
<main>
...omitted...
</main>
</>
);
};

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

export default Home;
import Head from "next/head";
import { type ReactElement } from 'react';
import { signIn, signOut, useSession } from "next-auth/react";
import { api } from "~/utils/api";
import Layout from "~/components/Layout";
import { type NextPageWithLayout } from "./_app";

const Home: NextPageWithLayout = () => {
const hello = api.example.hello.useQuery({ text: "from tRPC" }, {refetchOnWindowFocus: false,});

return (
<>
<Head>
...omitted for brevity...
</Head>
<main>
...omitted...
</main>
</>
);
};

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

export default Home;
Benja
BenjaOP2y ago
Thats great @FilipN !! I will try it out for sure, thanks for your advice
Aland
Aland2y ago
I don't understand, you want to have multiple Root layouts? So the layout from /user and /admin will be different? Why not use Route groups https://nextjs.org/docs/app/building-your-application/routing/route-groups
Benja
BenjaOP2y ago
beacuse that comes with the app foldering and on t3 stack version we used it's whitout it. So you can't set a layout.js file for this foldering structure. Thast one of the advantages of using the new Next.js version
Benja
BenjaOP2y ago
Routing: Pages and Layouts
Create your first page and shared layout with the Pages Router.
Want results from more Discord servers?
Add your server