Logo

Routing and Links

XMLUI implements client-side routing with URLs bound to UI views. You can use two different routing mechanisms: hash and standard. Both store the current location in the browser's address bar and work with the browser's history stack. However, they differ in the URLs they send to the backend server.

Hash routing (default)

If you don't have complete control over the server, you may not be able to configure it to retrieve the index.html file as a single-page app requires. Hash routing solves this issue. If your app is hosted at the myComp.com/accountapp URL (this URL serves the default index.html file from the server), navigation URLs will look like myComp.com/accountapp/#/leads/12 or myComp.com/accountapp/#/list?zip=98005. Even multiple hash marks may show up (for example, if you navigate to a bookmark: myComp.com/accountapp/#/leads/12#callhistory.
The server receives only the part of the URL (myComp.com/accountapp) that precedes the hash mark. The client-side routing mechanism uses the remainder to navigate within the app.
You can turn off hash routing and switch to standard routing using the app's config.json file.
{
  "name": "MyHashRoutedApp",
  "appGlobals": {
    "useHashBasedRouting": false
  }
};

Standard routing (optional)

When you navigate to an URL (e.g., refresh the current page), the browser sends the entire path to the web server. XMLUI apps are single-page web apps, and your web server should be configured accordingly.
For example, if your app is hosted at the myComp.com/accountapp URL (this URL serves the default index.html file from the server), it should be configured to retrieve the same index.html file even if the browser-sent URL contains path or query segments, such as myComp.com/accountapp/leads/12 or myComp.com/accountapp/list?zip=98005
If your web server is not configured this way, you'll receive 404 errors for the latest two (and similar) requests when refreshing the current page. Here's a sample nginx configuration.
events {
    worker_connections  1024;
}

http {
    include       /etc/nginx/mime.types;
    sendfile      on;

    server {
        root /path/to/your/project;

        index index.html index.htm;

        location ~ \.(js|css|png|jpg|jpeg|gif|ico|json|woff|woff2|ttf|eot|svg|xs|xmlui)$ {
            add_header Cache-Control "no-store, no-cache, must-revalidate, proxy-revalidate, max-age=0";
            expires off;
            try_files $uri =404;
        }

        location / {
        add_header Cache-Control "no-store, no-cache, must-revalidate, proxy-revalidate, max-age=0";
        expires off;
            try_files $uri $uri/ /index.html;
        }
    }
}

Serving from a subdirectory with proxy

If you need to serve your XMLUI app from a subdirectory path (e.g., /myapp/) while proxying to a running XMLUI server, you can use this nginx configuration. This approach works with both hash-based and standard routing.

Nginx configuration

server {
    listen 80;
    server_name example.com;

    location /myapp/ {
        rewrite ^/myapp(/.*)$ $1 break;
        proxy_pass http://127.0.0.1:8080;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;

        proxy_intercept_errors on;
        error_page 404 = /myapp/;
    }
}

Base path configuration

You'll need to configure the base path in your index.html:
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <script>
        window.__PUBLIC_PATH = '/myapp'
    </script>
    <script src="xmlui/0.9.23.js"></script>
</head>
<body>
</body>
</html>

How it works

This configuration:
  • Rewrites incoming /myapp/... requests to remove the prefix before proxying
  • Proxies requests to your XMLUI server running on port 8080
  • Redirects 404 errors back to /myapp/ to enable SPA fallback behavior
  • Works with both routing modes: Hash-based routing rarely triggers 404s, while standard routing uses the 404 redirect for client-side navigation

Routing mode compatibility

This subdirectory deployment works with both routing configurations:
Hash-based routing (useHashBasedRouting: true - default):
  • URLs: example.com/myapp/#/contacts
  • Server sees: example.com/myapp/
  • Behavior: Serves app normally from base path
Standard routing (useHashBasedRouting: false in config.json):
  • URLs: example.com/myapp/contacts
  • Server sees: example.com/myapp/contacts
  • Behavior: 404s redirect to /myapp/ which loads the app, then client-side routing takes over
XMLUI uses the specified links as absolute links (starting with a slash) or relative links, as the following example shows:
<App layout="vertical">
  <NavPanel>
    <NavLink to="/">Home</NavLink>
    <NavLink to="/contacts">Contacts</NavLink>
    <NavLink to="about">About</NavLink>
  </NavPanel>
  <Pages>
    <Page url="/">
      Home
    </Page>
    <Page url="contacts">
      Contacts
    </Page>
    <Page url="about">
      About
    </Page>
  </Pages>
</App>
<App layout="vertical">
  <NavPanel>
    <NavLink to="/">Home</NavLink>
    <NavLink to="/contacts">Contacts</NavLink>
    <NavLink to="about">About</NavLink>
  </NavPanel>
  <Pages>
    <Page url="/">
      Home
    </Page>
    <Page url="contacts">
      Contacts
    </Page>
    <Page url="about">
      About
    </Page>
  </Pages>
</App>
Here, / and /contacts are absolute links within the app, about is a relative link. As the NavPanel hierarchy is at the root level within the app, /contacts and contacts is the same URL.

Dynamic route segments

You can use parameter placeholders in the URLs as part of the route. These placeholders start with a colon and are followed by a valid identifier. In the target, you can query the value of these placeholders through the $routeParams context variable.
<App layout="vertical">
  <NavPanel>
    <NavLink to="/">Home</NavLink>
    <NavLink to="/account/Cameron">Cameron</NavLink>
    <NavLink to="/account/Joe">Joe</NavLink>
    <NavLink to="/account/Kathy">Kathy</NavLink>
  </NavPanel>
  <Pages>
    <Page url="/">
      Home
    </Page>
    <Page url="/account/:id">
      Account: {$routeParams.id}
    </Page>
  </Pages>
</App>
<App layout="vertical">
  <NavPanel>
    <NavLink to="/">Home</NavLink>
    <NavLink to="/account/Cameron">Cameron</NavLink>
    <NavLink to="/account/Joe">Joe</NavLink>
    <NavLink to="/account/Kathy">Kathy</NavLink>
  </NavPanel>
  <Pages>
    <Page url="/">
      Home
    </Page>
    <Page url="/account/:id">
      Account: {$routeParams.id}
    </Page>
  </Pages>
</App>

Using query parameters

You can also use query parameters in routes. The third link uses two query parameters, "from" and "to". The target page uses the $queryParams context variable to access them.
<App layout="vertical">
  <NavPanel>
    <NavLink to="/">Home</NavLink>
    <NavLink to="/contacts">Contacts</NavLink>
    <NavLink to="/report?from=December&to=February">Winter Report</NavLink>
  </NavPanel>
  <Pages>
    <Page url="/">
      Home
    </Page>
    <Page url="contacts">
      Contacts
    </Page>
    <Page url="/report">
      Reported period: {$queryParams.from}-{$queryParams.to}
    </Page>
  </Pages>
</App>
try clicking Winter Report
<App layout="vertical">
  <NavPanel>
    <NavLink to="/">Home</NavLink>
    <NavLink to="/contacts">Contacts</NavLink>
    <NavLink to="/report?from=December&to=February">Winter Report</NavLink>
  </NavPanel>
  <Pages>
    <Page url="/">
      Home
    </Page>
    <Page url="contacts">
      Contacts
    </Page>
    <Page url="/report">
      Reported period: {$queryParams.from}-{$queryParams.to}
    </Page>
  </Pages>
</App>
When the app visits a particular target in its available routes, the NavLink component matching with the visited route is marked as active, and it gets a visual indication (a blueish left border), like in this example:
<App layout="vertical">
  <NavPanel>
    <NavLink to="/">Home</NavLink>
    <NavLink to="/about">About</NavLink>
  </NavPanel>
  <Pages>
    <Page url="/">
      Home
    </Page>
    <Page url="/about">
      About this app
    </Page>
  </Pages>
</App>
Active links
<App layout="vertical">
  <NavPanel>
    <NavLink to="/">Home</NavLink>
    <NavLink to="/about">About</NavLink>
  </NavPanel>
  <Pages>
    <Page url="/">
      Home
    </Page>
    <Page url="/about">
      About this app
    </Page>
  </Pages>
</App>
When you start the app, the route is "/" (by default) and matches the Home page's route specification. Thus, Home is marked as the active link. When you click About, the route changes to "/about," so the active link becomes About (its route specification matches the current route):
As a NavLink activity is based on matching, multiple active links may exist simultaneously. The following example demonstrates such a situation:
<App display layout="vertical">
  <NavPanel>
    <NavLink to="/">Home</NavLink>
    <NavLink to="/report?from=December&to=February">Winter Report</NavLink>
    <NavLink to="/report?from=June&to=August">Summer Report</NavLink>
  </NavPanel>
  <Pages>
    <Page url="/">
      Home
    </Page>
    <Page url="/report">
      Reported period: {$queryParams.from}-{$queryParams.to}
    </Page>
  </Pages>
</App>
<App display layout="vertical">
  <NavPanel>
    <NavLink to="/">Home</NavLink>
    <NavLink to="/report?from=December&to=February">Winter Report</NavLink>
    <NavLink to="/report?from=June&to=August">Summer Report</NavLink>
  </NavPanel>
  <Pages>
    <Page url="/">
      Home
    </Page>
    <Page url="/report">
      Reported period: {$queryParams.from}-{$queryParams.to}
    </Page>
  </Pages>
</App>
Query parameters are not considered to be part of the route. So, in this sample, the Winter report and Summer report match the same route, "/report." If you select any of them, both links are marked active:
The semantic meaning of routes is analogous to routes used at the backend. When you send two requests with the same routes but different query parameters, they will reach the same backend endpoint. Of course, that endpoint may consider the query parameters, process them, and respond differently. However, this differentiation is not in the routing but in the processing mechanism.
This site is an XMLUI™ app.