Blog

How to Build a Responsive App Shell in React and Next.js

Build a responsive React app shell for Next.js with accessible landmarks, sidebar navigation, mobile drawers, stable layouts, and focused client code.

ReactNext.jsApp ShellResponsive Design

A responsive app shell is the frame around a web application: header, navigation, main content, optional aside, and footer. When the shell is well designed, product teams can add routes without rebuilding navigation and page structure each time.

When it is poorly designed, every screen inherits layout shifts, inconsistent spacing, inaccessible landmarks, and mobile navigation that feels bolted on.

This tutorial focuses on application structure. For a broader look at selecting dashboard components, read how to choose a React dashboard component library.

Define regions before styling

Begin with the document structure. Most application shells need a small set of clear regions:

  • A header for product identity and global actions.
  • Navigation for primary destinations.
  • Main content for the current route.
  • An optional complementary region for contextual information.
  • A footer when the product needs persistent legal or status links.

Use native landmarks wherever possible. A header, nav, main, aside, and footer give assistive technology a useful map without additional ARIA roles.

Only one main landmark should represent the page's primary content. Give multiple navigation regions distinct accessible names, such as “Primary” and “Account,” so they are not announced as identical destinations.

Keep the Next.js layout server-first

The root or route-group layout is a good place to compose stable structure. It does not need to become a Client Component simply because one menu button is interactive.

Keep the outer layout server-renderable, then isolate stateful behavior in a small client component. For example, the mobile navigation trigger and drawer can own open and close state while the header, main region, and route content remain server-first.

This boundary reduces the amount of JavaScript involved in the shell and helps content render without waiting for hydration. The guide to using component libraries without breaking Server Components explains this split in more detail.

Use one navigation model at every breakpoint

Desktop and mobile navigation should expose the same information architecture. Rendering two unrelated link lists creates drift: a new destination appears in the sidebar but not the mobile menu, or active-state logic behaves differently.

Define navigation as shared data and render it through the appropriate desktop and mobile presentation. Keep labels, URLs, permissions, and grouping in one source of truth.

On wide screens, a Sidebar may remain visible beside the main content. On narrow screens, the same links can move into a Drawer opened from an IconButton.

Make mobile navigation a real dialog-like interaction

A mobile drawer should do more than slide into view. It needs predictable interaction behavior:

  • The trigger exposes whether the navigation is open.
  • Focus moves into the drawer when appropriate.
  • Escape closes it.
  • Focus does not wander into obscured page content.
  • Closing returns focus to the trigger.
  • Choosing a destination closes the temporary navigation.

Also respect reduced-motion preferences. The navigation must remain understandable when the transition is shortened or removed.

Prevent the shell from causing layout shifts

Shell dimensions should be stable enough that route content does not jump during hydration or data loading. Avoid calculating the desktop sidebar width in an effect after the first paint. Use CSS layout and breakpoints for structural decisions whenever possible.

Grid is particularly useful for an app shell because named regions can describe a header, sidebar, main, and aside without fixed offsets. The main column should use minmax(0, 1fr) so wide tables or code blocks do not force the entire viewport to overflow.

Set a deliberate maximum width only when the product benefits from it. Dense dashboards often need fluid space, while settings forms may be more readable in a narrower content container.

Design active navigation states carefully

Users should be able to identify their current location without relying on colour alone. Combine visual treatment with a programmatic current-page indicator where the component API supports it.

Nested routes need an explicit matching rule. A link to /reports might be active for /reports/monthly, but the home link / should not appear active for every path. Keep this matching logic in one place and test similar route prefixes.

Breadcrumbs can complement primary navigation on deep routes. They should describe hierarchy, not repeat a flat list of recently visited pages. Boreal UI includes Breadcrumbs and PageHeader components for consistent route context.

Plan for long content and constrained heights

Application shells run into problems when viewport height is short, browser zoom is high, or navigation contains many items. Do not lock the whole page to 100vh and accidentally clip content.

Allow the document to grow. If a region needs independent scrolling, label it clearly and ensure keyboard users can reach its content. Modern viewport units can help on mobile, but the layout should still tolerate browser chrome and text enlargement.

Test at 200% zoom and with long translated labels. A shell that works only at one laptop resolution is not responsive.

Add skip navigation

Persistent navigation may contain dozens of links. A skip link lets keyboard users move directly to the main content on every route.

Place the skip link near the start of the document, keep it visible on focus, and point it to a stable id on the main region. Verify that focus actually moves to the destination rather than only scrolling visually.

Build the shell from composable pieces

A useful component model provides slots or regions rather than assuming every application has the same header and navigation. Boreal UI's AppShell composes header, sidebar, aside, main, and footer content while leaving the product-specific contents to the consumer.

That separation matters. The shell should own layout relationships; your application should own route links, account actions, permissions, and business context.

Test the shell as infrastructure

Before duplicating the pattern across the product, verify:

  1. Every route has one clear main landmark.
  2. The skip link works with a keyboard.
  3. Active navigation is correct on nested routes.
  4. Mobile navigation opens, closes, and restores focus.
  5. Wide content does not break the viewport.
  6. Zoom and long labels do not hide actions.
  7. Server-rendered content remains outside unnecessary client boundaries.
  8. Reduced-motion preferences are respected.

You can explore these relationships in the Boreal UI dashboard demo and then compare the implementation options in the AppShell documentation. A good React app shell becomes quiet infrastructure: predictable enough that users and developers can focus on the route itself.