.claude/skills/solidjs/solid-impl/solid-impl-routing/SKILL.md
Use when implementing client-side routing, navigation, or URL parameter handling in SolidJS applications. Prevents incorrect router setup, misused navigation hooks, and non-lazy-loaded route bundles. Covers Router/HashRouter setup, A component, useNavigate, useParams, useSearchParams, useBeforeLeave, lazy loading, route preloading, and config-based routing. Keywords: @solidjs/router, Route, useNavigate, useParams, useSearchParams, lazy, route guard, client-side routing.
npx skillsauth add OpenAEC-Foundation/OpenAEC-Workspace-Composer solid-impl-routingInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
npm install @solidjs/router
Requires SolidJS v1.8.4 or later.
| 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 | 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 |
| 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 |
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.
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")!);
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>
);
}
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>;
}
<A> Componentimport { 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.
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>;
}
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>
);
}
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} />
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>
);
}
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)} />;
}
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} />
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} />
import { usePreloadRoute } from "@solidjs/router";
function NavBar() {
const preload = usePreloadRoute();
return (
<nav>
<A href="/dashboard" onMouseEnter={() => preload("/dashboard")}>
Dashboard
</A>
</nav>
);
}
<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
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.
development
Use when integrating Vite with a backend framework, rendering Vite assets from server-side templates, or setting up dev/production HTML serving. Prevents incorrect manifest.json traversal and missing CSS chunk resolution in production. Covers build.manifest configuration, .vite/manifest.json structure, ManifestChunk properties, dev mode HTML setup, production rendering, CSS/JS chunk resolution, and modulepreload polyfill. Keywords: backend integration, manifest.json, ManifestChunk, Django, Laravel, Rails, modulepreload.
development
Use when encountering dev server startup failures, HMR issues, proxy errors, CORS blocks, or module not found errors during development. Prevents misconfiguring server.hmr behind reverse proxies and forgetting appType: 'custom' in middleware mode. Covers HMR full-reload debugging, proxy configuration, CORS setup, HTTPS certificates, server.fs.strict violations, port conflicts, WebSocket failures, file watcher issues, and middleware mode. Keywords: dev server, HMR, proxy, CORS, HTTPS, WebSocket, port conflict, server.fs.strict, middleware mode, file watcher.
development
Use when encountering pre-bundling errors, dependency resolution failures, stale cache issues, or slow development server startup. Prevents excluding CJS dependencies from pre-bundling (which breaks runtime module resolution) and misconfiguring optimizeDeps. Covers CJS/ESM conversion failures, missing dependency auto-discovery, optimizeDeps configuration, monorepo linked dependencies, cache invalidation, browser cache staleness, and large dependency tree performance. Keywords: pre-bundling, optimizeDeps, CJS, ESM, cache, dependency resolution, monorepo, node_modules/.vite.
development
Use when encountering Vite build failures, chunk size warnings, or version-specific build errors. Prevents the common mistake of using deprecated rollupOptions in v8 or misconfiguring build targets and minifiers. Covers Rolldown/Rollup bundling failures, CSS minification errors, sourcemap problems, library mode build failures, BundleError handling, and asset processing errors. Keywords: build error, Rolldown, chunk size, sourcemap, library mode, minify, BundleError, rollupOptions, build.target.