SuperAPI AI Directory

solid-impl-routing

General & other

View repository

Updated 10-15 22:18

About this skill

solid-impl-routing

Quick Reference

Installation

npm install @solidjs/router

Requires SolidJS v1.8.4 or later.

Router Types

Router URL Style Use Case
Router /path Production apps with server support
HashRouter /#/path Static hosting without server rewrites
MemoryRouter In-memory Testing, no browser history

Hook Quick Reference

Hook Returns Purpose
useNavigate() (to, options?) => void Programmatic navigation
useParams() Params Dynamic route parameter values
useSearchParams() [params, setParams] Query string read/write
useLocation() Location Current pathname, search, hash, state
useMatch(() => path) Accessor<Match | undefined> Check if path matches current route
useIsRouting() Accessor<boolean> True during navigation transitions
useBeforeLeave(callback) void Route guard (unsaved changes warning)
usePreloadRoute() (href) => void Trigger preloading on hover
useCurrentMatches() Accessor<Match[]> All matched route segments

React Router vs Solid Router

Concept React Router (WRONG) Solid Router (CORRECT)
Link component <Link to="/path"> <A href="/path">
Route element element={<Home />} component={Home}
Router hook useRouter() useNavigate()
Lazy import React.lazy(...) lazy(...) from solid-js
Loader data useLoaderData() createAsync(() => query(...))
Route loader loader prop preload prop

Critical Warnings

NEVER use element={<Component />} on a Route. This React pattern creates the component immediately, bypassing Solid Router's deferred rendering. ALWAYS use component={Component} (passing the reference, not a JSX call).

NEVER import Link from @solidjs/router — the component is called A, not Link. Using Link causes an import error.

NEVER import useRouter — this does not exist in Solid Router. ALWAYS use useNavigate() for programmatic navigation.

NEVER destructure useParams() at the top level — the returned object is a reactive proxy. Destructuring breaks reactivity. ALWAYS access params.id directly in JSX or inside tracked scopes.

NEVER call useSearchParams()[1] with a full replacement object expecting it to clear other params — it merges by default. To remove a param, set it to undefined.


Router Setup

Basic Router with JSX Routes

import { Router, Route } from "@solidjs/router";
import { lazy } from "solid-js";
import { render } from "solid-js/web";

const Home = lazy(() => import("./pages/Home"));
const About = lazy(() => import("./pages/About"));
const User = lazy(() => import("./pages/User"));

function App() {
  return (
    <Router>
      <Route path="/" component={Home} />
      <Route path="/about" component={About} />
      <Route path="/users/:id" component={User} />
    </Router>
  );
}

render(() => <App />, document.getElementById("root")!);

Router with Root Layout

import { Router, Route } from "@solidjs/router";
import type { RouteSectionProps } from "@solidjs/router";

function Layout(props: RouteSectionProps) {
  return (
    <div>
      <nav>
        <A href="/">Home</A>
        <A href="/about">About</A>
      </nav>
      <main>{props.children}</main>
    </div>
  );
}

function App() {
  return (
    <Router root={Layout}>
      <Route path="/" component={Home} />
      <Route path="/about" component={About} />
    </Router>
  );
}

Config-Based Routing

import { Router } from "@solidjs/router";
import type { RouteDefinition } from "@solidjs/router";
import { lazy } from "solid-js";

const routes: RouteDefinition[] = [
  { path: "/", component: lazy(() => import("./pages/Home")) },
  { path: "/about", component: lazy(() => import("./pages/About")) },
  {
    path: "/users",
    component: lazy(() => import("./pages/Users")),
    children: [
      { path: "/:id", component: lazy(() => import("./pages/UserDetail")) },
    ],
  },
];

function App() {
  return <Router>{routes}</Router>;
}

Navigation

The <A> Component

import { A } from "@solidjs/router";

<A href="/dashboard">Dashboard</A>
<A href="/users" activeClass="font-bold" inactiveClass="text-gray" end>Users</A>
<A href="/settings" replace noScroll state={{ from: "nav" }}>Settings</A>

ALWAYS use end on root paths (/) to prevent matching every route.

Programmatic Navigation

import { useNavigate } from "@solidjs/router";

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

  const handleLogin = async () => {
    await performLogin();
    navigate("/dashboard", { replace: true });
  };

  return <button onClick={handleLogin}>Log In</button>;
}

Navigate Component (Redirect)

import { Navigate } from "@solidjs/router";
import { Show } from "solid-js";

function ProtectedRoute(props: RouteSectionProps) {
  const user = useUser();
  return (
    <Show when={user()} fallback={<Navigate href="/login" />}>
      {props.children}
    </Show>
  );
}

Route Parameters and Search Params

Dynamic Parameters

import { useParams } from "@solidjs/router";

function UserProfile() {
  const params = useParams();
  // ALWAYS access params.id directly — NEVER destructure
  return <h1>User: {params.id}</h1>;
}

// Route: <Route path="/users/:id" component={UserProfile} />

Search Parameters

import { useSearchParams } from "@solidjs/router";

function ProductList() {
  const [search, setSearch] = useSearchParams();

  return (
    <div>
      <p>Page: {search.page ?? "1"}</p>
      <button onClick={() => setSearch({ page: String(Number(search.page ?? 1) + 1) })}>
        Next Page
      </button>
    </div>
  );
}

Route Guards

import { useBeforeLeave } from "@solidjs/router";
import { createSignal } from "solid-js";

function EditForm() {
  const [dirty, setDirty] = createSignal(false);

  useBeforeLeave((e) => {
    if (dirty() && !e.defaultPrevented) {
      e.preventDefault();
      if (window.confirm("Discard unsaved changes?")) {
        e.retry(true); // Force navigation
      }
    }
  });

  return <textarea onInput={() => setDirty(true)} />;
}

Lazy Loading and Preloading

Lazy Route Components

import { lazy } from "solid-js";

const Dashboard = lazy(() => import("./pages/Dashboard"));
const Settings = lazy(() => import("./pages/Settings"));

<Route path="/dashboard" component={Dashboard} />
<Route path="/settings" component={Settings} />

Route Preloading

import { Route, query, createAsync } from "@solidjs/router";
import type { RoutePreloadFuncArgs } from "@solidjs/router";

const getProduct = query(async (id: string) => {
  const res = await fetch(`/api/products/${id}`);
  return res.json();
}, "product");

function preloadProduct({ params }: RoutePreloadFuncArgs) {
  getProduct(params.id); // Fire-and-forget, warms cache
}

// Route definition
<Route path="/products/:id" component={ProductPage} preload={preloadProduct} />

Hover Preloading

import { usePreloadRoute } from "@solidjs/router";

function NavBar() {
  const preload = usePreloadRoute();

  return (
    <nav>
      <A href="/dashboard" onMouseEnter={() => preload("/dashboard")}>
        Dashboard
      </A>
    </nav>
  );
}

Nested Routes

<Router>
  <Route path="/users" component={UsersLayout}>
    <Route path="/" component={UsersList} />
    <Route path="/:id" component={UserDetail} />
    <Route path="/:id/settings" component={UserSettings} />
  </Route>
</Router>

The parent UsersLayout receives props.children which renders the matched child route.


Route Match Filters

<Route
  path="/users/:id"
  component={UserPage}
  matchFilters={{ id: /^\d+$/ }}
/>

The route only matches when id is numeric. Non-matching URLs fall through to other routes or 404.


Reference Links

Official Sources