Next.js Discord

Discord Forum

Individual page.tsx files for each value of dynamic route param [tab]

Unanswered
Spinone Italiano posted this in #help-forum
Open in Discord
Spinone ItalianoOP
Say I have the folder structure
/app/[tab]/
- layout.tsx
- page.tsx

In order to encapsulate logic, it would be nice to have a page.tsx for each value of tab.

/app/[tab]/
- songs.tsx
- artists.tsx
- playlists.tsx

Where songs, artists, playlists are all values of tab.

I've come up with this.

// /app/[tab]/page.tsx
import SearchArtistsPage from "./artists";
import SearchAlbumsPage from "./albums";
import SearchPlaylistsPage from "./playlists";
import SearchSongsPage from "./songs";

export default async function SearchPage({ searchParams, params }) {
    const { tab } = await params;

    return (
        <>
            {tab === 'songs' && <SearchSongsPage searchParams={searchParams} params={params} />}
            {tab === 'albums' && <SearchAlbumsPage searchParams={searchParams} params={params} />}
            {tab === 'playlists' && <SearchPlaylistsPage searchParams={searchParams} params={params} />}
            {tab === 'artists' && <SearchArtistsPage searchParams={searchParams} params={params} />}
        </>
    )
}


Is this the way to solve this problem? Is it bad practice for some reason?

12 Replies

// /app/[tab]/page.tsx
import SearchArtistsPage from "./artists";
import SearchAlbumsPage from "./albums";
import SearchPlaylistsPage from "./playlists";
import SearchSongsPage from "./songs";

export default async function SearchPage({ searchParams, params }) {
    const { tab } = await params;

    return (
        <>
            {tab === 'songs' && <SearchSongsPage searchParams={searchParams} params={params} />}
            {tab === 'albums' && <SearchAlbumsPage searchParams={searchParams} params={params} />}
            {tab === 'playlists' && <SearchPlaylistsPage searchParams={searchParams} params={params} />}
            {tab === 'artists' && <SearchArtistsPage searchParams={searchParams} params={params} />}
        </>
    )
}


This would be a way but you will need some sort of catch all at the end to redirect to not found.

Or if they are the inly for vakues they don't need dynamic routing.

Just create:

app/songs/page.tsx
app/artists/page.tsx
etc

Sometimes its easy to over complicate stuff.

The [something] functionality is only needed for if you have no idea what that would be like for example a blog slug or something
Spinone ItalianoOP
Thank you!

My bad I forgot to mention something crucial. All of these share the same layout.tsx which displays a list of tabs and highlights the currently selected tab, so it needed the [tab] param.

In the case where I have them in separate folders, I don't get the tab value for the layout.

app/songs/page.tsx
app/artists/page.tsx
Show me your layout code.
Spinone ItalianoOP
// app/[tab]/layout.tsx
import SearchResultTabs from "@/src/components/SearchResultTabs";

export default function SearchTabLayout({ children, params }) {
    return (
        <section>
            <SearchResultTabs />
            {children}
        </section>
    )
}


// @/src/components/SearchResultTabs
'use client'

import { Tabs } from "@mantine/core";
import { redirect, useParams, useRouter } from "next/navigation";

export default function SearchResultTabs() {
    const router = useRouter();
    const { search, tab } = useParams();

    return (
        <Tabs
            // keepMounted={true}
            defaultValue={tab || 'songs'}
            onChange={(tab) => {
                router.push(`/search/${search}/${tab}`);
            }}
        >
            <Tabs.List>
                <Tabs.Tab value="songs" >
                    Songs
                </Tabs.Tab>
                <Tabs.Tab value="playlists" >
                    Playlists
                </Tabs.Tab>
                <Tabs.Tab value="artists" >
                    Artists
                </Tabs.Tab>
                <Tabs.Tab value="albums" >
                    Albums
                </Tabs.Tab>
            </Tabs.List>
        </Tabs>
    )
}
As if it hink you means the same thing. I use this in my sidebar for showing the active page:

"use client";

import Link from "next/link";
import {usePathname} from "next/navigation";
import LogoComponent from "@/components/common/logo/LogoComponent";

const Sidebar = () => {
    const pathname = usePathname();

    // Helper function to compute class names.
    // For the dashboard link, require an exact match.
    const navItemClass = (href: string): string => {
        if (href === "/dashboard") {
            return pathname === href
                ? "block p-2 rounded bg-sky-300 text-slate-900"
                : "block p-2 rounded text-gray-700 dark:text-slate-300 hover:bg-sky-100 dark:hover:bg-sky-800";
        }
        // For other links, use startsWith.
        return pathname.startsWith(href)
            ? "block p-2 rounded bg-sky-300 text-slate-900"
            : "block p-2 rounded text-gray-700 dark:text-slate-300 hover:bg-sky-100 dark:hover:bg-sky-800";
    };

    return (
        <aside className="h-full w-64 border-r border-gray-200 bg-white p-4 dark:border-slate-700 dark:bg-slate-800">
            <div className="mb-6">
                <LogoComponent className="mx-auto h-12 w-auto" />
            </div>
            <nav>
                <ul className="space-y-2">
                    <li>
                        <Link
                            href="/dashboard"
                            className={navItemClass("/dashboard")}
                        >
                            Dashboard
                        </Link>
                    </li>
                    <li>
                        <Link
                            href="/dashboard/posts"
                            className={navItemClass("/dashboard/posts")}
                        >
                            Posts
                        </Link>
                    </li>
                    <li>
                        <Link
                            href="/dashboard/categories"
                            className={navItemClass("/dashboard/categories")}
                        >
                            Categories
                        </Link>
                    </li>
                    <li>
                        <Link
                            href="/dashboard/tags"
                            className={navItemClass("/dashboard/tags")}
                        >
                            Tags
                        </Link>
                    </li>
                    <li>
                        <Link
                            href="/dashboard/authors"
                            className={navItemClass("/dashboard/authors")}
                        >
                            Authors
                        </Link>
                    </li>
                    <li>
                        <Link
                            href="/dashboard/gallery"
                            className={navItemClass("/dashboard/gallery")}
                        >
                            Gallery
                        </Link>
                    </li>
                </ul>
            </nav>
        </aside>
    );
};

export default Sidebar;
And this allows you to have them as individual page.tsx files
This sidebar is imported in my layout FYI
Spinone ItalianoOP
Thank you again!

Yeah I think we are essentially doing the same thing.

Your way uses string parsing of the pathname, mine uses the dynamic route param with imports for each page.

I guess I was just wondering if there was anything inheritly bad in my method: importing a page in a page.

Anything about caching or something that makes the page components special and using them the way I'm doing being wrong...
I’m guessing there would be a slight performance increase in the static page.tsx over the dynamic route. As even though you know the potential options for it next has no built in way of identifying them so I’d say performance would be the benefit but it may negligible!
Spinone ItalianoOP
Intuitively that seems right to me! I'll probably switch to your method for that reason
thanks again for your help
No problem, in the long run will probably lead to cleaner code as you can do your string parsing like me in the layout or nav components! And just focus on the actual page components in the pages! Can sort of forget about it after you’ve wrote it!