Routing
React views use file-based routing: the router derives the route of each view from the path of its file in src/main/frontend/views. This page calls that directory views.
The router builds on React Router. Links, programmatic navigation, and the hooks that read parameters come from the react-router package: <NavLink>, useNavigate(), useParams(), and useSearchParams(). For examples of these, see Navigation and Layouts.
View Files
A view is a .tsx or .jsx file in views whose default export is a React component. Its route is the path of the file relative to views, without the extension. For example, the route of views/customers/list.tsx is /customers/list.
Some file and directory names have a special meaning:
| Name | Meaning |
|---|---|
| The view for the path of the directory itself. The route of |
| A layout that wraps the other views of the directory and its subdirectories. See Layouts. |
| A required route parameter. It can be a file, such as |
| An optional route parameter. |
| A wildcard parameter. It matches the rest of the path, including several segments. |
| Ignored by the router. This applies to both files and directories, so use it for components and utilities that aren’t views. |
The router also follows these rules:
-
Names other than
@indexand@layoutthat start with@are an error. -
A directory can’t contain a file and a subdirectory with the same name. Put an
@index.tsxfile in the subdirectory instead. -
A directory that contains an optional parameter can’t have an
@index.tsxfile. -
The router ignores a file that doesn’t contain
export default, and logs an error. It ignores empty files without an error. -
The router ignores
.tsand.jsfiles inviews, and logs a warning for each. Keep such files elsewhere, or start their names with_.
Route Parameters
The useParams() hook of React Router returns the parameter values of the current route. Each parameter has the name given in the braces, except a wildcard parameter: React Router always names it *, whatever the file name says.
Source code
views/docs/{...path}.tsx
views/docs/{...path}.tsximport { useParams } from 'react-router';
export default function DocsView() {
const { '*': path } = useParams();
return <h1>Document: {path}</h1>;
}A wildcard view also takes the routes of the Flow views under its directory: the router shows the wildcard view instead of those Flow views. In an application with Flow views, don’t put a wildcard view directly in views. To customize the page that’s shown when no view matches the path, replace the not-found view of Flow, as described in Custom Exception Handlers.
Layouts
A @layout.tsx file wraps the views of its directory and of its subdirectories. The layout component renders the <Outlet /> component of React Router where the view goes. Layouts nest: the layout of a subdirectory renders inside the layout of its parent directory.
The router loads most views only when they are first shown, as described for the lazy option below. Wrap the outlet in a <Suspense> boundary to show something while a view loads:
Source code
views/@layout.tsx
views/@layout.tsximport { Suspense } from 'react';
import { Outlet } from 'react-router';
import { ProgressBar } from '@vaadin/react-components/ProgressBar.js';
export default function MainLayout() {
return (
<main>
<Suspense fallback={<ProgressBar indeterminate />}>
<Outlet />
</Suspense>
</main>
);
}A view with the skipLayouts option renders without any layout. A Flow layout can also wrap React views. See Flow Layouts Around React Views.
View Configuration
A view file can export a config object of the type ViewConfig, which configures the route, the title, the menu item, and the access rules of the view:
Source code
views/customers/@index.tsx
views/customers/@index.tsximport type { ViewConfig } from '@vaadin/hilla-file-router/types.js';
export const config: ViewConfig = {
title: 'Customers',
menu: { order: 1, icon: 'vaadin:users' },
rolesAllowed: ['ADMIN'],
};
export default function CustomersView() {
return <h1>Customers</h1>;
}The router reads the config object when it builds the route list, without running the rest of the file. Write it as an object literal whose values are literals: it can’t refer to constants, functions, or imports. The only exception is the key tag for translation keys, described in the i18n guide.
All the options are optional:
title-
The title of the view. Menu items use it unless the menu item has a title of its own, and
useViewConfig()returns it. Without it, the title comes from the name of the component, withViewremoved and a space before each capital letter. For example, the title ofCustomerListViewisCustomer List. The router doesn’t set the title of the browser page. A layout can do that withuseViewConfig(). route-
Replaces the part of the route that comes from the file name, using the path syntax of React Router. For example, with
route: 'hi', the route ofviews/greeting/hello.tsxis/greeting/hi. menu-
The menu item of the view, with these options:
title-
The title of the menu item. Defaults to the title of the view.
order-
The position of the item in the menu, in ascending order. Items without an order come after the items that have one. Items with the same order are sorted by their path.
icon-
The icon of the menu item. The router passes it on as it is, so it can be an icon name, such as
vaadin:users, or the path to an image. exclude-
If
true, the view isn’t in the menu.
loginRequired-
If
true, only an authenticated user can open the view. rolesAllowed-
The roles that can open the view. The user needs at least one of them.
skipLayouts-
If
true, neither React nor Flow layouts wrap the view. This suits a login view, for example. flowLayout-
Whether a Flow layout annotated with
@Layoutwraps the view. Defaults totruewhen such a layout covers the route of the view. See Flow Layouts Around React Views. lazy-
If
true, the browser loads the code of the view only when the view is first shown. Defaults totruefor all views except the views with the routes/and/login, which load with the application. detail-
Any further data about the view, for example for rendering the menu. Its type is the type parameter of
ViewConfig.
The loginRequired and rolesAllowed options control which views the user can open, and which views the menu shows. They don’t protect any data. Protect the browser-callable services that the view calls on the server. See View Access Control.
Reading the Configuration
The useViewConfig() hook returns the configuration of the view that’s shown, or undefined while the router shows a Flow view. A layout can use it to show the title of the current view:
Source code
views/@layout.tsx
views/@layout.tsximport { useViewConfig } from '@vaadin/hilla-file-router/runtime.js';
import { Outlet } from 'react-router';
export default function MainLayout() {
const title = useViewConfig()?.title;
return (
<>
<header>{title}</header>
<Outlet />
</>
);
}Creating Menu Items
The createMenuItems() function returns the items for a navigation menu. Each item has the properties to, title, icon, and detail. The to property is the path of the view, and the others come from its configuration.
The server sends the list of views when the browser loads the page. It leaves out the views that the user isn’t allowed to open. The function then leaves out layouts, the views that have the exclude option, and the views whose route has a parameter. The list also contains the Flow views annotated with @Menu, as described in Flow Views in the Menu.
Give ViewConfig and createMenuItems() the same type parameter to type the detail data:
Source code
tsx
import { createMenuItems } from '@vaadin/hilla-file-router/runtime.js';
import type { ViewConfig } from '@vaadin/hilla-file-router/types.js';
import { NavLink } from 'react-router';
type ViewDetail = { description: string };
// In a view file:
export const config: ViewConfig<ViewDetail> = {
detail: { description: 'Browse the product catalog' },
};
// In the layout:
export function MainMenu() {
return (
<nav>
{createMenuItems<ViewDetail>().map(({ to, title, detail }) => (
<NavLink to={to} key={to} title={detail?.description}>
{title}
</NavLink>
))}
</nav>
);
}The useViewConfig() hook takes the type of the whole configuration as its type parameter, such as useViewConfig<ViewConfig<ViewDetail>>().
For a menu built with the Side Navigation component, see Building a Navigation Menu.
The Route Configuration File
The file routes.tsx configures the router. By default, Vaadin generates it in src/main/frontend/generated with this content:
Source code
tsx
import { RouterConfigurationBuilder } from '@vaadin/hilla-file-router/runtime.js';
import Flow from 'Frontend/generated/flow/Flow';
import fileRoutes from 'Frontend/generated/file-routes';
export const { router, routes } = new RouterConfigurationBuilder()
.withFileRoutes(fileRoutes)
.withFallback(Flow)
.protect()
.build();To change the configuration, copy the file to src/main/frontend and edit the copy. Vaadin uses it instead of the generated file. Restart the application after adding the file. After that, Vaadin picks up changes to it while the application runs.
The RouterConfigurationBuilder class has these methods:
withFileRoutes(fileRoutes)-
Adds the views in
views. The argument is the default export of the generatedfile-routes.tsfile. withReactRoutes(routes)-
Adds the given React Router route objects. The builder merges them with the other routes by path. The
handleproperty of a route object holds its view configuration, such ashandle: { title: 'Log In' }. withFallback(component, config)-
Adds a route for the paths that no other route matches, in every branch of the route tree. With the
Flowcomponent, the server handles those paths and shows the Flow view with the route. This method also callswithLayout()with the same component. Theconfigargument is optional. Only the first call has an effect. withLayout(component)-
Wraps the views that have the
flowLayoutoption in the given component, such asFlow. Only the first call has an effect. protect(redirectPath)-
Checks the
loginRequiredandrolesAllowedoptions in the browser. When the user can’t open a view, the router goes toredirectPathinstead, or to/loginwithout an argument. It gets the user from the authentication context. See View Access Control. build(options)-
Returns an object with the
routerand theroutes. The optionaloptionsare passed to thecreateBrowserRouter()function of React Router.
The following example adds a login view that isn’t in views, so that it can be outside the layouts, and sends users who can’t open a view to it:
Source code
src/main/frontend/routes.tsx
src/main/frontend/routes.tsximport { RouterConfigurationBuilder } from '@vaadin/hilla-file-router/runtime.js';
import Flow from 'Frontend/generated/flow/Flow';
import fileRoutes from 'Frontend/generated/file-routes';
import LoginView from 'Frontend/components/LoginView';
export const { router, routes } = new RouterConfigurationBuilder()
.withFileRoutes(fileRoutes)
.withReactRoutes([{ path: '/login', element: <LoginView />, handle: { title: 'Log In' } }])
.withFallback(Flow)
.protect('/login')
.build();Vaadin checks the file when it builds the frontend, and the build fails unless:
-
the file exports
routerandroutes, and -
in an application with Flow views, the file passes the
Flowcomponent fromFrontend/generated/flow/FlowtowithFallback(), or adds theserverSideRoutesexported by the same file to its routes.
Without a call to withFileRoutes(), the router doesn’t use the files in views. Vaadin still processes them as views, so don’t keep other .tsx and .jsx files there.