Skip to content
All essays
CraftMarch 27, 202613 min

SolidJS Routing: Building Navigation with Solid Router

Master SolidJS routing with Solid Router. Learn about routes, navigation, parameters, nested routes, and advanced routing patterns.

Ü
Ümit Uz
Mobile & Full Stack Developer

Solid Router provides a powerful, type-safe routing solution for SolidJS applications. With features like automatic code splitting, parallel data loading, and seamless integration with Solid's reactivity, it offers everything needed for modern single-page applications.

Installation and Setup

Install Solid Router:

bash
npm install @solidjs/router

Basic Routing Setup

Setting Up Routes

tsx
// App.tsx
import { Router } from '@solidjs/router';
import Home from './pages/Home';
import About from './pages/About';
import NotFound from './pages/NotFound';

function App() {
  return (
    <Router>
      <Routes>
        <Route path="/" component={Home} />
        <Route path="/about" component={About} />
        <Route path="*404" component={NotFound} />
      </Routes>
    </Router>
  );
}

Route Configuration

tsx
import { render } from 'solid-js/web';
import { Router, Routes, Route } from '@solidjs/router';
import Home from './pages/Home';
import Products from './pages/Products';
import ProductDetail from './pages/ProductDetail';
import Login from './pages/Login';
import Dashboard from './pages/Dashboard';

render(() => (
  <Router>
    <Routes>
      <Route path="/" element={<Home />} />
      <Route path="/products" element={<Products />} />
      <Route path="/products/:id" element={<ProductDetail />} />
      <Route path="/login" element={<Login />} />
      <Route path="/dashboard" element={<Dashboard />} />
    </Routes>
  </Router>
), document.getElementById('root'));

Use the Link component for declarative navigation:

tsx
import { Link } from '@solidjs/router';

function Navigation() {
  return (
    <nav>
      <Link href="/">Home</Link>
      <Link href="/products">Products</Link>
      <Link href="/about">About</Link>
    </nav>
  );
}
tsx
import { NavLink } from '@solidjs/router';

function Navigation() {
  return (
    <nav>
      <NavLink href="/" activeClass="active">
        Home
      </NavLink>
      <NavLink href="/products" activeClass="active">
        Products
      </NavLink>
      <NavLink href="/about" activeClass="active">
        About
      </NavLink>
    </nav>
  );
}

Programmatic Navigation

Use useNavigate for programmatic navigation:

tsx
import { useNavigate } from '@solidjs/router';

function LoginForm() {
  const navigate = useNavigate();

  const handleLogin = async (credentials: Credentials) => {
    const success = await login(credentials);

    if (success) {
      navigate('/dashboard');
    } else {
      navigate('/login', { replace: true });
    }
  };

  return (
    <form onSubmit={(e) => {
      e.preventDefault();
      handleLogin(/* credentials */);
    }}>
      {/* form fields */}
    </form>
  );
}
tsx
const navigate = useNavigate();

// Navigate with options
navigate('/dashboard', {
  replace: true,  // Replace current history entry
  scroll: true,   // Scroll to top
  state: { fromLogin: true },  // Pass state
});

// Navigate back/forward
navigate(-1);  // Back
navigate(1);   // Forward
navigate(-2);  // Back two pages

Route Parameters

Path Parameters

tsx
// Route definition
<Route path="/users/:id" component={UserDetail} />

// Component
function UserDetail() {
  const params = useParams<{ id: string }>();

  return (
    <div>
      <h1>User ID: {params.id}</h1>
      {/* Fetch user data using params.id */}
    </div>
  );
}

Multiple Parameters

tsx
// Route: /users/:userId/posts/:postId
function PostDetail() {
  const params = useParams();

  return (
    <div>
      <p>User: {params.userId}</p>
      <p>Post: {params.postId}</p>
    </div>
  );
}

Query Parameters

tsx
import { useSearchParams } from '@solidjs/router';

function ProductList() {
  const [searchParams, setSearchParams] = useSearchParams();

  const page = () => searchParams.page || '1';
  const category = () => searchParams.category || 'all';

  const setCategory = (category: string) => {
    setSearchParams({ category, page: '1' });
  };

  const nextPage = () => {
    setSearchParams({
      category: category(),
      page: String(parseInt(page()) + 1),
    });
  };

  return (
    <div>
      <select onInput={(e) => setCategory(e.currentTarget.value)}>
        <option value="all">All Categories</option>
        <option value="electronics">Electronics</option>
        <option value="clothing">Clothing</option>
      </select>

      <button onClick={nextPage}>Next Page</button>

      <div>
        Showing page {page()} of {category()} products
      </div>
    </div>
  );
}

Nested Routes

Nested Route Structure

tsx
function App() {
  return (
    <Router>
      <Routes>
        <Route path="/" component={Layout}>
          <Route path="/" element={<Home />} />
          <Route path="/products" element={<Products />} />
          <Route path="/products/:id" element={<ProductDetail />} />
          <Route path="/dashboard" element={<DashboardLayout />}>
            <Route path="/dashboard" element={<DashboardHome />} />
            <Route path="/dashboard/settings" element={<Settings />} />
            <Route path="/dashboard/profile" element={<Profile />} />
          </Route>
        </Route>
      </Routes>
    </Router>
  );
}

function Layout(props: { children: any }) {
  return (
    <div>
      <Navigation />
      <main>{props.children}</main>
      <Footer />
    </div>
  );
}

function DashboardLayout(props: { children: any }) {
  return (
    <div class="dashboard">
      <Sidebar />
      <main>{props.children}</main>
    </div>
  );
}

Nested Route Parameters

tsx
function App() {
  return (
    <Router>
      <Routes>
        <Route path="/users/:userId" component={UserLayout}>
          <Route path="/users/:userId" element={<UserOverview />} />
          <Route path="/users/:userId/posts" element={<UserPosts />} />
          <Route path="/users/:userId/posts/:postId" element={<PostDetail />} />
        </Route>
      </Routes>
    </Router>
  );
}

function UserLayout() {
  const params = useParams<{ userId: string }>();

  return (
    <div>
      <h1>User: {params.userId}</h1>
      <Outlet />
    </div>
  );
}

Data Loading

Route Data Loaders

tsx
import { RouteSectionProps, useRouteData } from '@solidjs/router';

// Data loader function
async function loader(props: RouteSectionProps) {
  const userId = props.params.userId;
  const response = await fetch(`/api/users/${userId}`);
  const user = await response.json();
  return { user };
}

// Route with loader
<Route
  path="/users/:userId"
  component={UserDetail}
  data={loader}
/>

// Component using loaded data
function UserDetail() {
  const data = useRouteData();

  return (
    <Show when={data()}>
      {(data) => (
        <div>
          <h1>{data().user.name}</h1>
          <p>{data().user.email}</p>
        </div>
      )}
    </Show>
  );
}

Parallel Data Loading

tsx
async function loadDashboardData() {
  const [user, notifications, stats] = await Promise.all([
    fetch('/api/user').then(r => r.json()),
    fetch('/api/notifications').then(r => r.json()),
    fetch('/api/stats').then(r => r.json()),
  ]);

  return { user, notifications, stats };
}

<Route
  path="/dashboard"
  component={Dashboard}
  data={loadDashboardData}
/>

Reactive Data Loading

tsx
function ProductList() {
  const params = useParams();
  const [products] = createResource(() => params.category, async (category) => {
    const response = await fetch(`/api/products?category=${category}`);
    return response.json();
  });

  return (
    <Show when={products()}>
      {(products) => (
        <div>
          <For each={products()}>
            {(product) => (
              <div>{product.name}</div>
            )}
          </For>
        </div>
      )}
    </Show>
  );
}

Route Guards

Authentication Guard

tsx
function ProtectedRoute(props: { component: any }) {
  const [isAuthenticated] = useAuthenticated();
  const navigate = useNavigate();

  createEffect(() => {
    if (!isAuthenticated()) {
      navigate('/login', { replace: true });
    }
  });

  return <Show when={isAuthenticated()}>
    {props.component}
  </Show>;
}

// Usage
<Route
  path="/dashboard"
  element={
    <ProtectedRoute component={<Dashboard />} />
  }
/>

Role-Based Access

tsx
function RoleGuard(props: {
  roles: string[];
  component: any;
}) {
  const [user] = useUser();
  const navigate = useNavigate();

  const hasAccess = createMemo(() => {
    return props.roles.some(role => user()?.roles?.includes(role));
  });

  createEffect(() => {
    if (!hasAccess()) {
      navigate('/unauthorized', { replace: true });
    }
  });

  return <Show when={hasAccess()}>
    {props.component}
  </Show>;
}

// Usage
<Route
  path="/admin"
  element={
    <RoleGuard roles={['admin']} component={<AdminPanel />} />
  }
/>

Redirects

Programmatic Redirect

tsx
function OldRoute() {
  const navigate = useNavigate();

  onMount(() => {
    navigate('/new-route', { replace: true });
  });

  return <p>Redirecting...</p>;
}

Conditional Redirect

tsx
function LoginPage() {
  const [isAuthenticated] = useAuthenticated();
  const navigate = useNavigate();

  createEffect(() => {
    if (isAuthenticated()) {
      navigate('/dashboard', { replace: true });
    }
  });

  return <LoginForm />;
}

Route Transitions

Transition Effect

tsx
import { useTransition } from '@solidjs/router';

function PageTransition() {
  const location = useLocation();
  const [transitioning] = useTransition();

  return (
    <div classList={{ transitioning: transitioning() }}>
      <Outlet />
    </div>
  );
}

Custom Transition Animation

tsx
function AnimatedRoute() {
  const [exiting, setExiting] = createSignal(false);

  return (
    <div
      class="page"
      classList={{ exiting: exiting() }}
      onAnimationEnd={() => setExiting(false)}
    >
      <Outlet />
    </div>
  );
}

Error Handling

Error Boundary

tsx
import { ErrorBoundary } from 'solid-js';

function App() {
  return (
    <Router>
      <ErrorBoundary fallback={(error) => <ErrorPage error={error} />}>
        <Routes>
          {/* routes */}
        </Routes>
      </ErrorBoundary>
    </Router>
  );
}

function ErrorPage(props: { error: Error }) {
  return (
    <div class="error-page">
      <h1>Oops! Something went wrong</h1>
      <p>{props.error.message}</p>
      <Link href="/">Go Home</Link>
    </div>
  );
}

Route-Level Error Handling

tsx
<Route
  path="/users/:userId"
  element={<UserDetail />}
  data={async (props) => {
    try {
      const user = await fetchUser(props.params.userId);
      return { user };
    } catch (error) {
      throw redirect('/404');
    }
  }}
/>

Best Practices

  1. 1Use Type Safety: Leverage TypeScript for route params
  2. 2Optimize Data Loading: Use parallel loading when possible
  3. 3Implement Guards: Protect routes properly
  4. 4Handle Errors: Provide error boundaries
  5. 5Lazy Load Routes: Code split for performance
  6. 6Use Proper Navigation: Prefer Link over manual navigation

Conclusion

Solid Router provides a comprehensive routing solution for SolidJS applications with features like type-safe navigation, nested routes, data loading, and route guards. By mastering these concepts, you can build complex, navigable applications with confidence.

The combination of SolidJS's reactivity and Solid Router's powerful features makes building single-page applications both efficient and enjoyable. Use these patterns to create scalable, maintainable routing solutions in your SolidJS projects.

Enjoyed this essay? Share it.

Related essays

Next essay
SolidJS Testing: Comprehensive Guide to Testing Solid Applications