Search code examples
next.jsweb-applicationsnavigationnext-router

NextJS Sidebar How to push routes on the main content


I have a sidebar in a NextJS application and a main component placed in a row. The Sidebar has multiple items clicking on each item updates the main-content <Preview/> by pushing a route (Updates the url), But this is rerendering both sidebar and main-content. My understanding is the components are incorrectly placed which is causing the issue. Right now I have placed both sidebar and main component inside the <Home/>

How should the folder structure and components structure be? Consider that Main component is a dynamic component (renders based on id passed to it).

This is the folder structure

Folder Structure

project-root /
    ├──src
    │    ├── app /
    │    │   ├── models /
    │    │   │   ├── Log.ts
    │    │   ├── services /
    │    │   │   ├── LogService.ts
    │    │   ├── components /
    │    │   │   ├── Sidebar.tsx
    │    │   │   ├── Navbar.tsx
    │    │   │   ├── Preview.tsx
    │    │   │   └── Home.tsx
    │    │   │   │
    │    │   ├── logs /
    │    │   │   ├──[id]
    │    │   │   │   ├── page.tsx
    │    │   └── layout.tsx
    │    │   └── global.css
    │    │   └── page.tsx

This is the dynamic page logs/[id]/page.tsx

// logs/[id]/page.tsx
import Home from '@/app/_components/Home';
import LogService from '../../_services/logService';

// This is required for dynamic routing in runtime
export const dynamicParams = true;

export async function generateStaticParams() {
    const logService = new LogService();
    const logs = await logService.fetchLogs();
    return logs.map(log => ({ id: log.id }));
}

export default function Page({ params }: { params: { id: string } }) {
    const { id } = params;
    return <Home id={id} />
};
// _components/Home.tsx

"use client";

import IconButton from "./IconButton";
import PSNavbar from "./PSNavbar";
import Pastelog from "./Pastelog";
import Preview from './Preview';
import Sidebar from './Sidebar';
import { Theme } from './ThemeSwitcher';

export default function Home({ id }: { id: string | null }) {
    const [showSideBar, setShowSideBar] = useState<boolean>(true);
    const [logs, setLogs] = useState<Log[]>([]);
    const [loading, setLoading] = useState<boolean>(false);
    const [selectedLogId, setSelectedLogId] = useState<string | null>(id);

...

    return (
        <div className={`flex h-screen ${theme == Theme.DARK ? 'darkTheme' : 'lightTheme'}`}>
            {
                (showSideBar && <IconButton
                    onClick={() => setShowSideBar(!showSideBar)}>
                    <ViewSidebarRoundedIcon />
                </IconButton>
                )
            }

            <div>
                {showSideBar && (
                    <Sidebar
                        loading={loading}
                        logs={logs}
                        id={selectedLogId}
                        onLogClick={(id) => {
                            if (id) {
                                router.push('/logs/$id');
                                setSelectedLogId(id!);
                            } else {
                                setSelectedLogId(null);
                                router.push('/');
                            }
                        }}
                    />
                )}
            </div>

            {/* Main content */}
            <div className={`grow overflow-y-auto transition-all duration-800 ${showSideBar ? 'pl-64' : 'pl-0'}`}>
                <div className="flex flex-col h-full">
                    <PSNavbar
                        sideBarIcon={!showSideBar}
                        onToggleSidebar={() => setShowSideBar(!showSideBar)}
                    />
                    {
                        selectedLogId ? (<Preview
                            id={selectedLogId}
                            showNavbar={false}
                        />) :
                            (<Pastelog />)
                    }
                </div>
            </div>
        </div>
    );
}

The issue with this approach is I would like to update the url when a list item is selected in the sidebar It works, but this is triggering rerendering of the entire home page (expected), but I would expect only main content to be rendered.

Also if a user visits the website with a link <baseurl>/sidebar-item, I would expect the app to load with sidebar-item selected (This is working fine as of now)

Complete source code here

This is the output of the above code which shows the sidebar rerendering

enter image description here


Solution

  • If sidebar content is common for all the page then you can move sidebar component to layout file. for example:

    export default function Layout ({ children } : { children : React.ReactNode}) {
     return <>
      <Sidebar />
      {children} //page content will go here
     </>
    }