
Security News
PolinRider: North Korea-Linked Supply Chain Campaign Expands Across Open Source Ecosystems
PolinRider expands across npm, Packagist, Go modules, and Chrome extensions, using hidden loaders to target developer environments.
@shadow-js/router
Advanced tools
Declarative client-side routing for ShadowJS applications with support for nested routes, layouts, transitions, and scroll restoration.
The ShadowJS Router requires @shadow-js/core as a peer dependency:
# Install both packages together
npm install @shadow-js/router @shadow-js/core
# Or if you already have @shadow-js/core installed
npm install @shadow-js/router
Important: This router is specifically designed for ShadowJS applications and requires @shadow-js/core to function properly. The router imports directly from @shadow-js/core and will not work without it.
For development and testing, you can use the development configuration:
# Copy the development package.json
cp package.json.dev package.json
# Install dependencies (includes @shadow-js/core)
npm install
This package uses a peer dependency approach for several reasons:
import { Router, Route, A, useLocation } from "@shadow-js/router";
function Navigation() {
return (
<nav>
<A href="/">Home</A>
<A href="/about">About</A>
<A href="/contact">Contact</A>
</nav>
);
}
function Home() {
const location = useLocation();
return (
<div>
<h1>Home</h1>
<p>Current path: {() => location.pathname}</p>
</div>
);
}
function About() {
return <h1>About Us</h1>;
}
function Contact() {
return <h1>Contact Us</h1>;
}
function NotFound() {
return <h1>404 - Page Not Found</h1>;
}
function App() {
return (
<Router notFound={NotFound}>
<Navigation />
<Route path="/" component={Home} />
<Route path="/about" component={About} />
<Route path="/contact" component={Contact} />
</Router>
);
}
import { useParams } from "@shadow-js/router";
function UserProfile() {
const params = useParams();
return (
<div>
<h1>User Profile</h1>
<p>User ID: {() => params().id}</p>
</div>
);
}
function App() {
return (
<Router>
<Route path="/users/:id" component={UserProfile} />
</Router>
);
}
Routes can be configured in two ways:
<Router>
<Route path="/" component={Home} />
<Route path="/about" component={About} />
<Route path="/users/:id" component={UserProfile} />
</Router>
const routes = [
{ path: "/", component: Home },
{ path: "/about", component: About },
{ path: "/users/:id", component: UserProfile },
];
<Router routes={routes} />;
ShadowJS Router uses a sophisticated matching algorithm:
/users matches only /users/users/:id matches /users/123/users/* matches /users/123/posts/users/:id? matches /users and /users/123function DashboardLayout({ children }) {
return (
<div className="dashboard">
<nav>
<A href="/dashboard">Overview</A>
<A href="/dashboard/users">Users</A>
<A href="/dashboard/settings">Settings</A>
</nav>
<main>{children}</main>
</div>
);
}
function DashboardOverview() {
return <h2>Dashboard Overview</h2>;
}
function App() {
return (
<Router>
<Route path="/dashboard" layout={DashboardLayout}>
<Route path="/" component={DashboardOverview} />
<Route path="/users" component={DashboardUsers} />
<Route path="/settings" component={DashboardSettings} />
</Route>
</Router>
);
}
<Router>The main router component that manages client-side routing for your application.
Props:
routes?: RouteConfig[] - Array of route configurations (alternative to children)children?: any - Child routes when using JSX syntaxtransition?: Transition - Default transition for all routesnotFound?: ComponentType - Component to render for unmatched routesmode?: "history" | "hash" - URL handling mode (default: "history")scroll?: ScrollBehavior - Scroll restoration behaviorExamples:
// JSX syntax
<Router notFound={NotFound} mode="history">
<Route path="/" component={Home} />
<Route path="/about" component={About} />
</Router>;
// Object syntax
const routes = [
{ path: "/", component: Home },
{ path: "/about", component: About },
];
<Router routes={routes} />;
<Route>Defines a route with its path and component.
Props:
path: string - Route path patterncomponent?: ComponentType - Component to render for this routelayout?: ComponentType - Layout component wrapping this routechildren?: RouteConfig[] - Nested routesredirect?: string | Function - Redirect path or functiontransition?: Transition - Route-specific transitionExamples:
// Basic route
<Route path="/home" component={Home} />
// Route with layout
<Route path="/dashboard" layout={DashboardLayout}>
<Route path="/" component={Overview} />
<Route path="/users" component={Users} />
</Route>
// Route with redirect
<Route path="/old-path" redirect="/new-path" />
// Dynamic route
<Route path="/users/:id" component={UserProfile} />
<A>Enhanced anchor tag for client-side navigation with automatic active states.
Props:
href: string - Navigation targetreplace?: boolean - Replace current history entrystate?: any - State to pass to the new routeclassName?: string | Function - CSS class (can be reactive)<a> tag propsExamples:
<A href="/home">Home</A>
<A href="/users" className={() => (location.pathname === "/users" ? "active" : "")}>
Users
</A>
<Redirect>Programmatically redirects to another route.
Props:
to: string - Redirect target pathExamples:
<Redirect to="/login" />
useLocation()Returns the current location object with reactive properties.
Returns: Store<Location>
Location Properties:
pathname: string - Current pathsearch: string - Query stringhash: string - Hash fragmentstate: any - Navigation stateExamples:
function CurrentPage() {
const location = useLocation();
return (
<div>
<p>Current path: {location.pathname}</p>
<p>Search: {location.search}</p>
<p>Hash: {location.hash}</p>
</div>
);
}
useParams<T>()Returns route parameters extracted from the current URL.
Returns: Store<T | undefined>
Examples:
function UserProfile() {
const params = useParams<{ id: string }>();
return <div>User ID: {params().id}</div>;
}
useSearchParams()Manages URL search parameters with reactive updates.
Returns: [Store<URLSearchParams>, SetterFunction]
Examples:
function SearchComponent() {
const [searchParams, setSearchParams] = useSearchParams();
const updateSearch = (query: string) => {
setSearchParams({ q: query, page: "1" });
};
return (
<input
value={searchParams().get("q") || ""}
onInput={(e) => updateSearch(e.target.value)}
/>
);
}
navigate(to, options?)Programmatically navigate to a new route.
Parameters:
to: string - Target pathoptions?: NavigateOptions - Navigation optionsNavigateOptions:
replace?: boolean - Replace current history entrystate?: any - State to pass to the new routeExamples:
// Basic navigation
navigate("/dashboard");
// Replace current history entry
navigate("/login", { replace: true });
// Pass state
navigate("/checkout", { state: { fromCart: true } });
redirect(to)Redirect to a new route (equivalent to navigate with replace: true).
Parameters:
to: string - Redirect targetExamples:
redirect("/login");
RouteConfig<P, T>Configuration object for defining routes programmatically.
Properties:
path: P - Route path patterncomponent?: ComponentType - Route componentlayout?: ComponentType - Layout componentchildren?: RouteConfig[] - Nested routesredirect?: string | Function - Redirect configurationtransition?: Transition - Route transitionPathParams<P>Type-safe route parameters extracted from path patterns.
Examples:
// For path "/users/:id/posts/:postId"
// PathParams = { id: string, postId: string }
NavigateOptions<T>Options for programmatic navigation.
Properties:
replace?: boolean - Replace history entrystate?: T - Navigation stateTransitionAnimation effect for route changes.
Types:
"none" - No transition"fade" - Fade transitionCustomTransition - Custom transition functionimport { navigate, redirect } from "@shadow-js/router";
function LoginForm() {
const [isLoggedIn, setIsLoggedIn] = useStore(false);
const handleLogin = () => {
setIsLoggedIn(true);
// Navigate after successful login
navigate("/dashboard", { replace: true });
};
const handleLogout = () => {
setIsLoggedIn(false);
// Redirect to home
redirect("/");
};
return (
<div>
<button onClick={handleLogin}>Login</button>
<button onClick={handleLogout}>Logout</button>
</div>
);
}
<Router transition="fade">
<Route path="/" component={Home} />
<Route path="/about" component={About} transition="slide" />
</Router>
Available transitions: "fade", "slide", "scale", "none"
<Router
scroll={(context) => {
if (context.action === "PUSH") {
window.scrollTo(0, 0); // Scroll to top on navigation
} else if (context.action === "POP") {
// Restore scroll on browser back/forward
// Default behavior handles this automatically
}
}}
>
{/* routes */}
</Router>
function ProtectedRoute({ component: Component }) {
const [isAuthenticated, setIsAuthenticated] = useStore(false);
return (
<Show when={() => isAuthenticated()} fallback={<Redirect to="/login" />}>
<Component />
</Show>
);
}
function App() {
return (
<Router>
<Route
path="/dashboard"
component={() => <ProtectedRoute component={Dashboard} />}
/>
</Router>
);
}
import { useSearchParams } from "@shadow-js/router";
function ProductList() {
const [searchParams, setSearchParams] = useSearchParams();
const category = () => searchParams().get("category") || "all";
const sort = () => searchParams().get("sort") || "name";
const updateFilters = (newFilters) => {
setSearchParams({
...Object.fromEntries(searchParams().entries()),
...newFilters,
});
};
return (
<div>
<select
value={() => category()}
onChange={(e) => updateFilters({ category: e.target.value })}
>
<option value="all">All Categories</option>
<option value="electronics">Electronics</option>
<option value="books">Books</option>
</select>
</div>
);
}
The router compiles route configurations into an optimized matching tree:
popstate and hashchange eventsThe router manages several reactive states:
import { useStore } from "@shadow-js/core";
import { Router, Route, useLocation } from "@shadow-js/router";
function App() {
const [user, setUser] = useStore(null);
return (
<Router>
<Route path="/" component={Home} />
<Route path="/profile" component={Profile} />
</Router>
);
}
// For server-side rendering or custom history management
const customHistory = {
push: (path) => {
/* custom push logic */
},
replace: (path) => {
/* custom replace logic */
},
go: (delta) => {
/* custom go logic */
},
};
// Pass to router
<Router history={customHistory}>{/* routes */}</Router>;
Routes can have their own styling:
// Route-specific styles
<Route path="/dashboard" component={Dashboard} className="dashboard-route" />;
// Conditional styling based on active route
function Navigation() {
const location = useLocation();
return (
<nav>
<A href="/" className={() => (location.pathname === "/" ? "active" : "")}>
Home
</A>
<A
href="/about"
className={() => (location.pathname === "/about" ? "active" : "")}
>
About
</A>
</nav>
);
}
function App() {
return (
<Router
notFound={() => <div>Custom 404 Page</div>}
onError={(error) => {
console.error("Route error:", error);
// Handle route errors
}}
>
{/* routes */}
</Router>
);
}
import { navigate } from "@shadow-js/router";
try {
await navigate("/protected-route");
} catch (error) {
if (error.code === "UNAUTHORIZED") {
navigate("/login");
}
}
// Preload routes on hover for better UX
function Link({ href, children }) {
const [isPreloading, setIsPreloading] = useStore(false);
return (
<A
href={href}
onMouseEnter={() => {
setIsPreloading(true);
// Preload route component
import(`./pages${href}.js`);
}}
className={() => (isPreloading() ? "preloading" : "")}
>
{children}
</A>
);
}
Routes automatically memoize components to prevent unnecessary re-renders.
import { render } from "@shadow-js/core";
import { Router, Route } from "@shadow-js/router";
describe("Routing", () => {
test("renders correct component for route", () => {
const container = document.createElement("div");
render(
<Router>
<Route path="/test" component={() => <div>Test Component</div>} />
</Router>,
container
);
// Navigate to test route
window.history.pushState({}, "", "/test");
expect(container.innerHTML).toContain("Test Component");
});
});
See the examples documentation for:
We welcome contributions! See the Contributing Guide for details.
MIT License - see LICENSE for details.
Built with ❤️ for the ShadowJS ecosystem.
FAQs
Minimal, type-safe router for ShadowJS
The npm package @shadow-js/router receives a total of 2 weekly downloads. As such, @shadow-js/router popularity was classified as not popular.
We found that @shadow-js/router demonstrated a healthy version release cadence and project activity because the last version was released less than a year ago. It has 1 open source maintainer collaborating on the project.
Did you know?

Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.

Security News
PolinRider expands across npm, Packagist, Go modules, and Chrome extensions, using hidden loaders to target developer environments.

Security News
Open source attacks are accelerating as AI coding agents pull in dependencies faster, with less human review.

Research
/Security News
Malicious Chrome and Firefox extensions posed as free VPNs while stealing clipboard data through later extension updates.