Routing turns a collection of views into an app. It maps URL hash patterns to views, handles back navigation, and keeps the browser's back button working. Once you set it up properly, navigation code almost disappears from your controllers. Let's build it from the ground up.
Defining routes in the manifest
Routes live in manifest.json under "sap.ui5" → "routing". You define a router config, a set of routes (URL patterns), and targets (the views to display).
"routing": {
"config": {
"routerClass": "sap.m.routing.Router",
"viewType": "XML",
"viewPath": "my.app.view",
"controlId": "app",
"controlAggregation": "pages"
},
"routes": [
{ "name": "overview", "pattern": "", "target": "overview" },
{ "name": "detail", "pattern": "detail/{orderId}", "target": "detail" }
],
"targets": {
"overview": { "viewName": "Overview", "viewLevel": 1 },
"detail": { "viewName": "Detail", "viewLevel": 2 }
}
}
The controlId points at your root App control. Each route's pattern becomes a hash URL like #/detail/4711 — deep-linkable and bookmarkable.
Navigating between pages
From a controller, grab the router and call navTo with the route name and parameters. The router builds the hash for you.
onOrderPress: function (oEvent) {
const sOrderId = oEvent.getSource().getBindingContext().getProperty("OrderId");
this.getOwnerComponent().getRouter().navTo("detail", { orderId: sOrderId });
}
Going back is just as clean. The standard pattern checks the browser history first, then falls back to a default route.
onNavBack: function () {
const oHistory = History.getInstance();
const sPreviousHash = oHistory.getPreviousHash();
if (sPreviousHash !== undefined) {
window.history.go(-1);
} else {
this.getOwnerComponent().getRouter().navTo("overview", {}, true);
}
}
Always use navTo with route names — never hardcode hash strings. If the pattern changes later, only the manifest needs updating.
Reading route parameters
The detail view needs the orderId from the URL. Attach to the route's patternMatched event in onInit and bind your view to the right entity.
onInit: function () {
this.getOwnerComponent().getRouter()
.getRoute("detail")
.attachPatternMatched(this._onRouteMatched, this);
},
_onRouteMatched: function (oEvent) {
const sOrderId = oEvent.getParameter("arguments").orderId;
this.getView().bindElement(`/Orders(${sOrderId})`);
}
This pattern keeps the URL as the single source of truth. Refresh the page on a detail URL and the same record loads — no state to lose.
Common pitfalls
- Missing viewLevel: without it, transitions can look wrong and the history stack behaves oddly on mobile.
- Forgetting async routes: in newer UI5 versions, views load asynchronously by default — don't assume a view exists synchronously after
navTo. - Nav in dialogs: routing swaps pages in the root control. A dialog is not a page — don't route to one; open it from the controller instead.
Named routes also make nested navigation easy. A master-detail app on a tablet can show both lists and details side by side, driven by the same route hierarchy — the router resolves which targets to fill based on the layout. You define this once in the manifest instead of writing device-specific navigation code in every controller.
Get the manifest routing right once and reuse the pattern in every app. It's one of the highest-leverage setups in a UI5 project.
What's the trickiest routing bug you've debugged? Share it in the comments.