Back to the blog

Keeping App Router Metadata on the Server While Moving Themed Content into Client Components

Keep Next.js App Router metadata in server route files while moving theme-dependent styled-components into dedicated Client Components. The change resolved the observed theme access error without making page.tsx a Client Component.

Introduction

I separated route metadata from themed rendering in a Next.js App Router implementation. Before the change, the page.tsx files were Server Components that rendered themed styled-components directly. SiteHeader and SiteFooter were Server Components too. Both paths threw theme access errors because the themed consumers executed outside the ThemeProvider context available to them. I kept the route files as Server Components and moved the themed consumers into explicit Client Components.

The distinction here is Server Component execution, not whether the resulting HTML is rendered on the server. Client Components can also be server-rendered, but Server Components cannot consume client context. A themed Client Component still needs an appropriate ThemeProvider ancestor; adding a client boundary doesn't remove that requirement.

After the change, SiteHeader and SiteFooter were Client Components, and the home route rendered its themed content through HomePageContent.tsx. I observed that the error disappeared after refresh. The subsequent configured lint checks also reported no issues.

The Rule That Shipped

I applied one rule across the marketing route files: page.tsx stays a Server Component so it can retain its metadata export. Any styled-components JSX in that file that depends on theming moves into a separate Client Component file, which page.tsx imports and renders.

The route file still owns metadata and composes the page. The extracted component owns the themed markup and supplies the client boundary needed to consume client context. Theme dependency is the reason for extraction—not the presence of JSX alone. The rule doesn't require moving every element in a route into a client file.

For SiteHeader and SiteFooter, I chose a broader boundary and marked the components themselves as Client Components. That was the implemented choice, not evidence that every part of either component needed to be client-side. The resulting structure makes the themed consumers easy to locate while keeping route metadata exports in Server Components.

Worked Example

The home route shows the before-and-after change most clearly. Its page.tsx was a Server Component containing styled-components that read theme values. Those consumers executed outside the ThemeProvider scope available to them and produced a theme access error. The issue wasn't that server-rendered HTML cannot use themed styling. The themed consumer was executing as a Server Component, where it could not consume client context.

I created HomePageContent.tsx as a dedicated Client Component and moved the themed markup into it. The home route's page.tsx then rendered HomePageContent instead of containing that markup directly. Since page.tsx remained a Server Component, it retained its metadata export.

That extraction established a client boundary for the themed content, but the content still needed an appropriate provider ancestor to receive a theme. The evidence doesn't identify the provider's exact location, so I can't describe that ancestry in more detail.

After the change and a refresh, the error no longer appeared. The subsequent lint run completed without issues. These are separate results: the refresh supplied the reported runtime observation, while lint reported only on the configured static checks.

Challenges and Tradeoffs

The main constraint was keeping metadata exports in place while allowing themed consumers to use client context. Making page.tsx a Client Component would conflict with retaining its metadata export there. I kept the route file on the server and added a client file for the themed content instead. That preserved the division of responsibility without requiring a different metadata arrangement.

The cost is more files and a boundary to maintain. A route change may now involve both the server route file and its client content. In return, theme-dependent code has a clear home outside the file responsible for metadata. It also gives reviewers a concrete check: when a new themed consumer appears in page.tsx, consider whether it belongs in the extracted client component.

Provider placement still matters. Putting a Client Component provider above a Server Component doesn't allow that Server Component to consume client context. Likewise, moving a consumer into a Client Component won't supply a theme if the appropriate provider ancestor is missing. The successful refresh supports the reported case, but it doesn't document the provider's placement in the tree or establish how other arrangements would behave.

Extracting smaller themed children from SiteHeader and SiteFooter remains a possible alternative. I chose to mark the whole components as Client Components. The available evidence doesn't establish whether a narrower extraction would have worked or reduced the client boundary, so I wouldn't present the broader conversion as the only viable design.

Verification Process

I keep the runtime observation separate from the lint result. After converting the shared components and restructuring the home route, the theme access error was no longer observed on refresh. The home route's worked case records the same outcome after its themed markup moved into HomePageContent.tsx. That supports the specific reported repair, not a claim that every render path was checked.

The configured lint checks subsequently completed without issues, meaning those checks found no reportable problems. That result doesn't establish a successful production compilation, provider availability at runtime, or correct behavior across every route. Neither a production build nor route-by-route runtime checks are established by the reported verification.

I also haven't confirmed whether every other marketing route had thrown the same runtime error before the change or was restructured preemptively to follow the rule. The consistent rollout is supported; a failure-and-recovery history for each route is not.

The follow-up checks would address those gaps directly: run the production build, exercise the changed routes, and confirm that the themed client consumers sit beneath the intended provider. These are proposed checks, not completed results. The available evidence establishes no automated test coverage beyond the lint result.

Conclusion

I kept page.tsx files as Server Components for metadata and moved theme-dependent styled-components into separate Client Component files. HomePageContent.tsx is the concrete home-route extraction, and SiteHeader and SiteFooter also became Client Components. The reported result was that the theme access error disappeared after refresh and the subsequent lint run completed cleanly.

The limits remain important. The exact framework error text is unspecified, the provider's placement is unknown, and prior runtime failures across every marketing route aren't established. Neither a production build nor broader automated testing is confirmed. The reusable rule is to separate route metadata from themed client consumers—not to assume that extraction alone guarantees context availability or that lint proves runtime correctness.