javascriptnode.jssolid-js

SolidJS Router: How to render different sidebar in a nested route?


I'm trying to create nested routes with common elements.

In my app as shown below, top level routes works fine, content changes and header remains in its place.

But every time when I want to create another one Routes/Router to keep sidebar also, I keep failing.

<Router>
  <div class="some wrap elements">
    <header>
      {/* 
      header menu, logo, etc 
      should be visible at any page
    */}
    </header>
    <div class="some container">
      <Routes>
        <Route path="/" component={Home} />
        <Route path="/profile" component={Profile} />
        {/* other routes */}
      </Routes>
    </div>
  </div>
</Router>

In the Profile component I would like to create one more Routes component and I were trying to:

<div class="another wrap elements">
  <div class="sidebar">which should stay on page if users is on any 'profile' page, like home /profile, /profile/settings, /profile/favorites etc</div>
  <Routes>
    <Route path="/profile/settings" component={Setting} />
    {/* other routes */}
  </Routes>
  
</div>

Following also does not work:

<Route path="/profile">
  <div>some static el (sidebar)</div>
  <Route path="/" component={Profile} />
  <Route path="/settings" component={Settings} />
</Route>

Creating a component Sidebar and including it in every Profile-related component works, but it is not what I am trying to do.


Solution

  • As of Solid Router v0.9 and later, you can use the root prop to render a layout component:

    const Layout: Component<RouteSectionProps> = (props) => {
      return (
        <main>
          <header>Header</header>
          {props.children}
          <footer>Footer</footer>
        </main>
      );
    }
    
    function App() {
      return (
        <Router root={Layout}>
          <Route path="/" component={Home} />
          <Route path="/about" component={About} />
        </Router>
      );
    }
    

    In the layout component, you can use the useLocation function or other utility functions to get the current location and return a different element.

    For example, we can render a different layout for the root directory:

    import { Route, Router, RouteSectionProps, useLocation } from "@solidjs/router";
    import { Component, createMemo, JSXElement } from "solid-js";
    import { render } from "solid-js/web";
    
    const Home = () => {
      return (
        <div>
          <h1>Home</h1>
          <div><a href="/about">Go to About</a></div>
        </div>
      );
    };
    
    const About = () => {
      return (
        <div>
          <h1>About</h1>
          <div><a href="/">Go to Home</a></div>
        </div>
      );
    };
    
    const Layout: Component<RouteSectionProps> = (props) => {
      const location = useLocation();
    
      return createMemo(() => {
        if (location.pathname === '/') {
          return (
            <main>
              <header>Home Header</header>
              {props.children}
              <footer>Home Footer</footer>
            </main>
          )
        }
    
        return (
          <main>
            <header>Header</header>
            {props.children}
            <footer>Footer</footer>
          </main>
        );
      }) as unknown as JSXElement;
    }
    
    function App() {
      return (
        <Router root={Layout}>
          <Route path="/" component={Home} />
          <Route path="/about" component={About} />
        </Router>
      );
    }
    
    render(() => <App />, document.body);
    

    Returning a memo from a component is a common technique for conditional rendering and is widely used in Solid's built-in components.

    While using useLocation gives us full control over layout behavior, there’s another approach that avoids conditional logic entirely. By nesting routes inside layout components, we can declare which layout to use directly in the routing structure.

    The following example demonstrates this approach:

    import { Route, Router, RouteSectionProps, useLocation } from "@solidjs/router";
    import { Component, createMemo, JSXElement } from "solid-js";
    import { render } from "solid-js/web";
    
    const Home = () => {
      return (
        <div>
          <h1>Home</h1>
          <div><a href="/about">go to about</a></div>
        </div>
      );
    };
    
    const About = () => {
      return (
        <div>
          <h1>About</h1>
          <div><a href="/">go to home</a></div>
        </div>
      );
    };
    
    const HomeLayout: Component<RouteSectionProps> = (props) => {
      return (
        <main>
          <header>Home Header</header>
          {props.children}
          <footer>Home Footer</footer>
        </main>
      );
    }
    
    const DefaultLayout: Component<RouteSectionProps> = (props) => {
      return (
        <main>
          <header>Header</header>
          {props.children}
          <footer>Footer</footer>
        </main>
      );
    }
    
    function App() {
      return (
        <Router>
          <Route path="/" component={HomeLayout}>
            <Route path="/" component={Home} />
          </Route>
          <Route component={DefaultLayout}>
            <Route path="/about" component={About} />
          </Route>
        </Router>
      );
    }
    
    render(() => <App />, document.body);
    

    Note: These examples are adapted from SolidJS: The Complete Guide. If you're interested in a comprehensive understanding of SolidJS, you may find it helpful: 👉 solid.courses/p/solidjs-the-complete-guide


    Note: The following example predates the Solid Router rewrite and remains for reference, offering a perspective on alternative layout solutions.

    To include a sidebar in specific components, you can render it directly—routing isn't required.

    If you'd like to populate the content of the sidebar differently depending on the route, you can conditionally render the sidebar’s content. In the example below, we render user-related data if there is a user ID. You can use props to pass data from a parent component if the data is already available there.

    Since the header element is defined outside of the route tree, it appears on all pages as expected. However, this isn’t the recommended method for sharing layout components.

    import { render, Show } from "solid-js/web";
    import { A, Route, Router, Routes, useParams } from "@solidjs/router";
    
    const Sidebar = () => {
      const params = useParams();
      const id = () => params.id;
    
      return (
        <Show when={id()} fallback={<div>Regular Content</div>}>
          <div>Specific Content for {id()}</div>
        </Show>
      );
    };
    
    const Users = () => {
      return (
        <div>
          Users Page
          <Sidebar />
        </div>
      );
    };
    
    const User = () => {
      return (
        <div>
          User Page
          <Sidebar />
        </div>
      );
    };
    
    const Home = () => {
      return (<div>Home Page</div>);
    };
    
    const App = () => {
      return (
        <div>
          <header>
            <A href="/">Home</A>{" "}
            <A href="/users">Users</A>{" "}
            <A href="/users/john">John</A>
          </header>
          <main>
            <Routes>
              <Route path="/" component={Home} />
              <Route path="/users" component={Users} />
              <Route path="/users/:id" component={User} />
            </Routes>
          </main>
        </div>
      );
    };
    
    render(() => (
      <Router>
        <App />
      </Router>
    ), document.body);
    

    Here's how you pass parameters from a route component to the sidebar component:

    import { A, Route, Router, Routes, useParams } from "@solidjs/router";
    import { Component } from "solid-js";
    import { render, Show } from "solid-js/web";
    
    const Sidebar: Component<{ greeting: string }> = (props) => {
      const params = useParams();
      const id = () => params.id;
    
      return (
        <Show when={id()} fallback={<div>Content for the rest</div>}>
          <div>Content for {id()}</div>
          <div>{props.greeting}</div>
        </Show>
      );
    };
    
    const Users = () => {
      return (
        <div>
          Users
          <Sidebar greeting="Hello From Users" />
        </div>
      );
    };
    
    const User = () => {
      return (
        <div>
          User Page
          <Sidebar greeting="Greetings from User" />
        </div>
      );
    };
    
    const Home = () => {
      return (<div>Home Page</div>);
    };
    
    const App = () => {
      return (
        <div>
          <header>
            <A href="/">Home</A>{" "}
            <A href="/users">Users</A>{" "}
            <A href="/users/john">John</A>
          </header>
          <main>
            <Routes>
              <Route path="/" component={Home} />
              <Route path="/users" component={Users} />
              <Route path="/users/:id" component={User} />
            </Routes>
          </main>
        </div>
      );
    };