qwik-router
v1.3.1
Published
A SPA-like router for Qwik.
Downloads
57
Maintainers
Readme
Table of Contents
- Table of Contents
- Why?
- See a Working Example
- Installing in your Project
- Initiating the router
- Reacting to Route Changes
- Navigating
- Component Routing
- ROADMAP:
- ⭐ Give us a Star ⭐
- Contributing
- Code Of Conduct
- Related Links
- License
Why?
Many Qwik native solutions, like routing, are very attached to Qwik City, which is great for most use cases. But sometimes, more than a file-based routing is needed.
Possible use cases are:
- Micro-frontends with Qwik, where you need to load different apps on the same page
- When you need a component-based routing with customized rules.
- When you need multiple routers on the same page.
Thinking of that, I created this router to be used with Qwik based on similar projects like vue-router and react-router.
This follows Qwik principles and is targeted for flexibility. If you don't have unique routing needs, you should use Qwik City.
This component has been used in production in a few projects but is still in beta.
I'll add more documentation and examples in the next few days.
See a Working Example
git clone https://github.com/dannyfranca/qwik-router.git
pnpm i
pnpm nx run qwik-router:serve
Installing in your Project
pnpm i qwik-router
or
yarn add qwik-router
or
npm i qwik-router
Initiating the router
To initiate the router, import the initRouter
and execute in the Root component giving a string URL as input.
This will create a Qwik context that will be changed every time the app navigates using the navigateTo
, the Link
component or the window
object popstate
event.
// root.tsx
import { component$ } from "@builder.io/qwik";
import { initRouter } from "qwik-router";
export default component$((props: { url: string }) => {
initRouter(props.url);
return <Child />;
});
Reacting to Route Changes
The route state is a reactive Qwik store with the interface RouteState
shared with a Qwik context.
Import the useRoute
and/or useParams
and set up a lifecycle task.
⚠️ Warning:
The useParams
hooks only works under a Router
component.
import { useVisibleTask$, useTask$, component$ } from "@builder.io/qwik";
import { isServer } from "@builder.io/qwik/build";
import { useRoute, useParams } from "qwik-router";
export default component$(() => {
const routeState = useRoute();
const paramsState = useParams(); // only works under a Router component
useVisibleTask$(
({ track }) => {
track(() => routeState.pathname);
track(() => routeState.search);
// react to pathname and search changes only when component becomes visible on the browser.
},
{
strategy: "intersection-observer",
}
);
useTask$(({ track }) => {
track(() => paramsState);
// react to any changes on the path parameters
});
useTask$(({ track }) => {
track(() => paramsState.id);
// react to any changes on the :id path parameters
});
useTask$(({ track }) => {
track(() => routeState);
if (isServer) {
// execute some code before components render on the server
} else {
// react to any changes on the browser
}
});
return <Child />;
});
The initRouter
also returns a router reactive state, just like useRouter
.
Notice the route state is based on the native URL API. It has full interface compatibility with the native URL, but it is not an instance of URL.
// root.tsx
import { component$ } from "@builder.io/qwik";
import { initRouter } from "qwik-router";
// notice that initRouter can only be executed under an optimized component. A.K.A `component$`
export default component$((props: { url: string }) => {
const router = initRouter(props.url);
router.hash; // #hash
router.pathname; // /path
router.search; // ?query=string
router.query; // { query: 'string' }
router.host; // host:port
router.origin; // http://host:port
router.protocol; // http:
router.port; // port
router.href; // http://host:port/path?query=string#hash
return <Child />;
});
Navigating
The useNavigate
Hook
This hook creates a navigation function.
import { component$ } from '@builder.io/qwik';
import { useNavigate } from 'qwik-router';
const App = component$(() => {
const nav = useNavigate();
return <Child onClick$={() => {
nav('/new-path?param=value#new-hash')
}} />;
});
The Link
Component
Use the Link component like an anchor tag. It uses the useNavigate
hook under the hood to navigate.
You can add an optional activeClassName
prop to config the class that will be added when the link matches the active path.
import { component$ } from '@builder.io/qwik';
import { Link } from 'qwik-router';
const NewPathNav = component$(() => {
return <Link activeClassName="active" href="/new-path">Go to New Path</Link>;
});
Component Routing
Inspired by the API of react-router
and vue-router
, the component routing uses the same underlying engine to identify the router and route to a configured component.
All you have to do is create a route config file and serve the Router
component with the routes
properties.
It is important to attribute the Component function to the component prop, not the JSX instance.
// routes.ts
import { RouterConfig } from 'qwik-router';
import { RouteComponent1 } from './components/RouteComponent1';
import { RouteComponent2 } from './components/RouteComponent2';
import { RouteComponent3 } from './components/RouteComponent3';
const routes: RouterConfig = [
{
path: '/:lang?/route-1', // Optional lang parameter. Matches /route-1 and /en/route-1
component: RouteComponent1,
},
{
path: '/:lang/route-2',
component: RouteComponent2,
},
{
path: '/:lang/route-3',
component: <RouteComponent3 />, // Wrong
},
];
The Router component has an optional defaultComponent
property to be used when no path is matched.
// App.tsx
import { component$ } from "@builder.io/qwik";
import { initRouter, Router } from 'qwik-router';
import { routes } from './routes.ts';
import { DefaultRouteComponent } from './components/DefaultRoute';
const App = component$((props: { url: string }) => {
initRouter(props.url);
return <Router routes={routes} defaultComponent={DefaultRouteComponent} />;
});
Matching Patterns
To check all possible advanced matching patterns, check the path-to-regexp documentation.
Multiple Routers and URL Parameters
The router supports URL parameters. You can access them in the component with the useParams
hook.
The reason not to use the useRoute
hook is because you can use multiple Route components, leading to different param states.
The Router
initializes the param state as a Context, so it can be accessed by any component under it using the useParams
hook.
// root.tsx
import { component$ } from "@builder.io/qwik";
import {
type RouterConfig,
initRouter,
useParams,
useRoute,
Router,
} from "qwik-router";
const DynamicRoute = component$(() => {
const route = useRoute();
const params = useParams(); // { language: 'en', page: 'home' }
return <div>I am DynamicRoute</div>;
});
const Route1 = component$(() => {
const route = useRoute();
const params = useParams(); // { lang: 'en' }
return <div>I am Route 1</div>;
});
const Route2 = component$(() => {
const route = useRoute();
const params = useParams(); // { lang: 'en' }
return <div>I am Route 2</div>;
});
const routeConf1: RouterConfig = [
{
path: "/:language/:page",
// /en/home -> useParams() will return { language: 'en', page: 'home' }
component: DynamicRoute,
},
];
const routeConf2: RouterConfig = [
{
path: "/:lang/route-1",
// /en/route-1 -> useParams() will return { lang: 'en' }
component: Route1,
},
{
path: "/:lang/route-2",
// /en/route-2 -> useParams() will return { lang: 'en' }
component: Route2,
},
];
export default component$((props: { url: string }) => {
initRouter(props.url);
return (
<>
<Router routes={routeConf1} />
<Router routes={routeConf2} />
</>
);
});
Roadmap
- [x] Dynamic Route Matching
- [x] Params in URL
- [x] Advanced Matching Patterns
- [x] Router Component
- [x] Link Component
- [x] Programmatic Navigation
- [x] Route State Compatible with native URL
- [ ] Navigating State
- [ ] Properties to Routes
- [ ] Scroll Behavior
- [ ] Transition Animations
- [ ] Navigation Guards
- [ ] Route Meta Fields
- [ ] Named Routes
- [ ] Named Views
- [ ] Passing Props to Routes
- [ ] Nested Routes
- [ ] Different History Modes
- [ ] more...
⭐ Give us a Star ⭐
It was quite a challenge to build this project, and we are very proud of it. We hope you enjoy it too.
If this project generated value for you, please give us a star on GitHub. It helps the project be discovered by other people.
Contributing
Want to contribute? 😀
Please read and follow our Contributing Guidelines to learn what are the right steps to take before contributing your time, effort and code.
Thanks 🙏
Code Of Conduct
Be kind to each other and please read our code of conduct.
Related Links
License
MIT