Skip to content

Refactor root layout to use server components and metadata API instead of client Head #8

@seanoliver

Description

@seanoliver

Problem

The root layout (src/app/layout.tsx) is currently a client component ('use client') that uses <Head> from next/head. This is incompatible with Next.js 13+ App Router and causes several issues:

  1. SSR Benefits Lost: Making the root layout a client component destroys server-side rendering benefits
  2. Metadata Not in Initial HTML: The <Head> component from next/head doesn't work properly in App Router - metadata like the RSS autodiscovery link isn't in the initial HTML, only added after client hydration
  3. Anti-Pattern: App Router has a proper metadata API that should be used instead
  4. Discovery Issues: Search engines and RSS readers may not discover the RSS feed since the link isn't in the initial SSR HTML

Current Implementation

// src/app/layout.tsx
'use client'  // ❌ Root layout should be server component

import Head from 'next/head'  // ❌ Doesn't work properly in App Router

export default function RootLayout({ children }: { children: React.ReactNode }) {
  const navRefs: NavRefsProps = NAV_ITEMS.reduce((acc: any, item) => {
    acc[item.name] = useRef(null)  // ❌ Client-side ref management
    return acc
  }, {})

  return (
    <html lang='en'>
      <Head>  // ❌ Should use metadata API instead
        <title>Sean Oliver</title>
        <meta property='og:title' content='Sean Oliver' key='title' />
        <link rel='alternate' type='application/rss+xml' ... />
      </Head>
      <body>
        <NavContext.Provider value={navRefs}>  // ❌ Context requires client component
          {children}
        </NavContext.Provider>
      </body>
    </html>
  )
}

Root Cause

The layout is a client component because it needs:

  • useRef() hook for navigation scroll refs
  • NavContext.Provider for passing refs to navigation components

Proposed Solution

1. Convert Layout to Server Component

Move metadata to the proper App Router metadata API:

// src/app/layout.tsx (Server Component)
import type { Metadata } from 'next'

export const metadata: Metadata = {
  title: 'Sean Oliver',
  description: 'Growth Engineer at Supabase...',
  openGraph: {
    title: 'Sean Oliver',
    // ...
  },
  alternates: {
    types: {
      'application/rss+xml': 'https://seanoliver.dev/feed.xml',
    },
  },
}

export default function RootLayout({ children }: { children: React.ReactNode }) {
  return (
    <html lang='en'>
      <body>
        <NavProvider>  {/* New client component wrapper */}
          <Header />
          <main>{children}</main>
          <Footer />
        </NavProvider>
      </body>
    </html>
  )
}

2. Create Client Component Wrapper for Navigation Context

// src/components/nav-provider.tsx
'use client'

import { useRef } from 'react'
import { NavContext } from '@/hooks/use-nav-context'
import { NAV_ITEMS } from '@/lib/constants'
import type { NavRefsProps } from '@/lib/types'

export function NavProvider({ children }: { children: React.ReactNode }) {
  const navRefs: NavRefsProps = NAV_ITEMS.reduce((acc: any, item) => {
    acc[item.name] = useRef(null)
    return acc
  }, {})

  return <NavContext.Provider value={navRefs}>{children}</NavContext.Provider>
}

3. Update Font Loading

Since fonts can't use useRef in server components:

// Keep font definitions at module level
const jetBrainsMono = localFont({ ... })
const monolisa = localFont({ ... })

// Apply via className instead of context
<html lang='en' className={clsx(jetBrainsMono.variable, monolisa.variable)}>

Benefits

Proper SSR: Root layout is server component, metadata in initial HTML
Better SEO: Search engines see RSS autodiscovery link immediately
Follows Best Practices: Uses App Router patterns correctly
Improved Performance: Less JavaScript shipped to client
Type Safety: Metadata API is fully typed
Future Proof: Aligns with Next.js direction

Implementation Checklist

  • Create NavProvider client component wrapper
  • Convert root layout to server component
  • Move all <Head> contents to metadata export
  • Move font className application to html element
  • Remove 'use client' directive from layout
  • Remove next/head import
  • Test navigation scroll behavior still works
  • Test theme provider still works
  • Verify RSS autodiscovery in view-source
  • Test with JavaScript disabled (SSR should work)

Technical Notes

  • The ThemeProvider from next-themes can remain as a client component child
  • Navigation scroll functionality will continue working via NavProvider
  • Font loading via localFont works in server components
  • This is a breaking change pattern but not a breaking change for users

References

Priority

Medium - This is an architectural improvement that should be done before the codebase grows larger. It doesn't break functionality but violates Next.js best practices and hurts SEO/discovery.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions