2 minute read

In this post, I’ll share my journey of implementing dynamic page titles in a Next.js application, including the challenges I faced and the solutions I discovered along the way.

The Initial Approach: Direct Component State

Like many developers, I started with what seemed like a straightforward task: displaying dynamic page titles based on the selected sidebar item in my Next.js application. My first attempt was refreshingly simple - just add a title to each page component:

export default function NotificationsPage() {
    return (
        <h1>Notifications</h1>
    )
}

DRY Principles

As the application grew, I realized I was repeating these titles in multiple places:

  • In the sidebar navigation
  • In the page headers
  • Potentially in breadcrumbs

This violated the DRY (Don’t Repeat Yourself) principle. I needed a better solution.

The Navigation-Driven Approach

I decided to make the navigation items the single source of truth. I enhanced my navigation items in AppLayout.tsx to include a pageTitle property:

const navigationItems = [
  { 
    name: 'Goal', 
    path: '/', 
    icon: '/goal.svg', 
    isVisible: true,
    pageTitle: 'Dashboard'
  },
  { 
    name: 'Notifications', 
    path: '/notifications', 
    icon: '/notification.svg', 
    isVisible: user ? true : false,
    pageTitle: 'Notifications'
  }
  // ... more items
]

The Context Conundrum

To make these titles available throughout the app, I created a PageContext.tsx:

const PageContext = createContext<PageContextType>({ currentPageTitle: undefined })

export function PageProvider({ children, currentPageTitle }: {
  children: React.ReactNode,
  currentPageTitle?: string 
}) {
  return (
    <PageContext.Provider value=>
      {children}
    </PageContext.Provider>
  )
}

The Server-Side Surprise

But then I hit a wall! When I tried to use the context in my page components:

// This caused an error!
const { currentPageTitle } = usePageContext()

I got the dreaded error:

Attempted to call usePageContext() from the server but usePageContext is on the client.

The Final Solution: Client Components to the Rescue

The solution was to create a dedicated client component for the pagetitle.tsx:

'use client'

import { usePageContext } from '@/context/PageContext'

export default function PageTitle() {
  const { currentPageTitle } = usePageContext()
  
  return (
    <h1 className="text-2xl font-semibold text-gray-900">
      {currentPageTitle}
    </h1>
  )
}

And use it in our server-side page components page.tsx:

export default function NotificationsPage() {
    return (
        <div>
            <PageTitle />
            {/* rest of the page content */}
        </div>
    )
}

Lessons Learned

  • Single Source of Truth: Keeping navigation and page metadata in one place makes maintenance easier

  • Server vs Client Components: Understanding Next.js’s server/client boundary is crucial

  • Component Separation: Sometimes creating a dedicated component for a small feature is the right approach

  • Context Usage: While contexts are powerful, they need to be used carefully in Next.js’s server-component world

  • Conclusion What started as a simple task turned into a journey through Next.js’s architecture and React best practices. The final solution is clean, maintainable, and properly handles the server/client component boundary. Remember: in web development, the simplest-looking features often lead to the most interesting learning experiences!

Updated: