Skip to main content

Overview

Client Side Routing

React Router enables "client side routing".

In traditional websites, the browser requests a document from a web server, downloads and evaluates CSS and JavaScript assets, and renders the HTML sent from the server. When the user clicks a link, it starts the process all over again for a new page.

Client side routing allows your app to update the URL from a link click without making another request for another document from the server. Instead, your app can immediately render some new UI and make data requests with fetch to update the page with new information.

This enables faster user experiences because the browser doesn't need to request an entirely new document or re-evaluate CSS and JavaScript assets for the next page. It also enables more dynamic user experiences with things like animation.

import { Route, RouterLink, Routes, SuspenseRouter } from '@archibald/core';

import { createRoot } from 'react-dom/client';

function App() {
return (
<Routes>
<Route
path="/"
element={
<div>
<h1>Hello World</h1>
<RouterLink to="about">About Us</RouterLink>
</div>
}
/>
<Route path="/about" element={<div>About</div>} />
</Routes>
);
}

createRoot(document.getElementById('root')).render(
<SuspenseRouter>
<App />
</SuspenseRouter>
);

Nested Routes

Nested Routing is the general idea of coupling segments of the URL to component hierarchy. ArchibaldRouter's nested routes were inspired by the routing system in ReactRouter v6.

This visualization might be helpful.

Dynamic Segments

Segments of the URL can be dynamic placeholders that are parsed and provided to various apis.

<Route path="projects/:projectId/tasks/:taskId" />

The two segments with : are dynamic, and provided to the following APIs:

// If the current location is /projects/abc/tasks/3
<Route element={<Task />} />;

function Task() {
// returned from `useParams`
const params = useParams();
params.projectId; // abc
params.taskId; // 3
}

function Random() {
const params = usePatternParams('/projects/:projectId/tasks/:taskId');
params.projectId; // abc
params.taskId; // 3
}

Ranked Route Matching

When matching URLs to routes, ArchibaldRouter will rank the routes according to the number of segments, static segments, dynamic segments, splats, etc. and pick the most specific match.

For example, consider these two routes:

<Route path="/teams/:teamId" />
<Route path="/teams/new" />

Now consider the URL is http://example.com/teams/new.

Even though both routes technically match the URL (new could be the :teamId), you intuitively know that we want the second route (/teams/new) to be picked. ArchibaldRouter's matching algorithm knows that, too.

With ranked routes, you don't have to worry about route ordering.

Most web apps have persistent navigation sections at the top of the UI, the sidebar, and often multiple levels. Styling the active navigation items so the user knows where they are (isActive) in the app is done easily with <RouterNavLink>.

<RouterNavLink activeClassName="active" />

Like HTML <a href>, <RouterLink to> and <RouterNavLink to> can take relative paths, with enhanced behavior with nested routes.

Given the following route config:

<Route path="home" element={<Home />}>
<Route path=":userId" element={<User />} />
<Route path="project/:projectId" element={<Project />}>
<Route path=":taskId" element={<Task />} />
</Route>
</Route>

Consider the url https://example.com/home/project/123, which renders the following route component hierarchy:

<Home>
<Project />
</Home>

If <Project /> renders the following links, the hrefs of the links will resolve like so:

In <Project> @ /home/project/123Resolved <a href>
<Link to="abc">/home/project/123/abc
<Link to="/home/007">/home/007

If <Home /> renders the following links, the hrefs of the links will resolve like so:

In <Home> @ /home/project/123Resolved <a href>
<Link to="007">/home/007
<Link to="/home/007">/home/007
<Link to="project/123/abc">/home/project/123/abc

Relative links are always relative to the route path they are rendered in, not to the full URL. That means if the user navigates deeper with <Link to="abc"> to <Task /> at the URL /home/project/123/abc, the hrefs in <Project> will not change (contrary to plain <a href>, a common problem with client side routers).

Skeleton UI with <Suspense>

Instead of waiting for the data for the next page, you can defer data so the UI flips over to the next screen with placeholder UI immediately while the data loads. It also waits 300ms by default before switching to the next page UI to avoid bad user experience (flashing Suspense fallback for a fraction of a ms on a fast network/device). In case this timeout exceeds the provided value fallback is rendered.

You can provide global fallback on a <Routes> level:

<Routes fallback={<GlobalFallback />}>
<Route path="/" element={<Home />} />
<Route path="/p/:productId" element={<ProductDetails />} />
<Route path="/about" element={<About />} />
</Routes>

You also can provide per page specific fallback on a <Route> level. In this case <GlobalFallback> will be used for every page unless page specific fallback is provided:

<Routes fallback={<GlobalFallback />}>
<Route path="/" element={<Home />} fallback={<HomeSkeleton />} />
<Route path="/p/:productId" element={<ProductDetails />} />
<Route path="/about" element={<About />} />
</Routes>

ArchibaldRouter provides a possibility for a nested routing which means you can have even deeper fallback configuration in your app:

<Routes fallback={<GlobalFallback />}>
<Route path="/" element={<AppLayout />} fallback={<AppFallback />}>
<Route path="p/:productId" element={<ProductDetails />} />
<Route index element={<Home />} fallback={<HomeSkeleton />} />
</Route>
<Route path="/about" element={<About />} />
</Routes>

In the example above the following is fallbacks will be used whenever waiting time exceeds the provided timeout (300ms):

Navigating toActive fallback
"/"<HomeSkeleton />
"/p/123"<AppFallback />
"/about"<GlobalFallback />

Language fix

ArchibaldRouter provides you with automatic language fix both for your URLs and Links based on environment configuration provided.

If you have configured de and en as your supported languages there will be automatic redirect whenever user tries to use unsupported language in the URL or language is completely missing.

Let's check an example:

  • supported languages are: de and fr
  • default language is de
Requested URLRedirectRendered URL
"/de"false"/de"
"/fr/p/123"false"/fr/p/123"
"/en/p/123"true"/de/p/123"
"/p/123"true"/de/p/123"

Known Limitation

When you have configured a pattern with a * operator in between it's not always guaranteed to fix the missing language 100% correct.

<Route path="/:language/*?/admin" element={<Admin />} />

Fixing of a language will result into:

Requested URLRedirectRendered URL
"/de/admin"false"/de/admin"
"/ua/admin"true"/de/admin"
"/admin"true"/de/admin"
"/text/admin"true"/de/admin"
"/internal/long/text/admin"true"/de/long/text/admin"

As you can see there is no way to know what is considered as a wrong language VS missing language as long as URL matches the provided pattern.

WARNING

THE CODE BELOW IS AN ASSUMPTION NOT THE ACTUALLY IMPLEMENTED LOGIC!

In case we would choose to always add language in front, you can end up in a situation with both correct and incorrect languages in the URL.

Let's assume we are always adding language to the pathname when the found language from the URL is not supported:

Requested URLRedirectRendered URL
"/de/admin"false"/de/admin"
"/ua/admin"true"/de/ua/admin"
"/admin"true"/de/admin"
"/text/admin"true"/de/text/admin"
"/internal/long/text/admin"true"/de/internal/long/text/admin"