static HttpService maybeIndexService(String basePath, Resource resource) throws IOException {
    String maybeContent = maybeResource(basePath, resource);
    if (maybeContent == null) return null;

    ServerCacheControl maxAgeMinute = ServerCacheControl.builder().maxAgeSeconds(60).build();

    return HttpFile.builder(HttpData.ofUtf8(maybeContent))
      .contentType(MediaType.HTML_UTF_8).cacheControl(maxAgeMinute)
      .build().asService();
  }
Zipkin-UI is a single-page application mounted at /zipkin. For simplicity, assume paths mentioned
below are relative to that. For example, the UI reads config.json, from the absolute path
/zipkin/config.json

<p>When looking at a trace, the browser is sent to the path "/traces/{id}". For the single-page
app to serve that route, the server needs to forward the request to "/index.html". The same
forwarding applies to "/dependencies" and any other routes the UI controls.

<p>Under the scenes the JavaScript code looks at {@code window.location} to figure out what the
UI should do. This is handled by a route api defined in the crossroads library.

<h3>Caching</h3>
<p>This includes a hard-coded cache policy, consistent with zipkin-scala.
<ul>
  <li>1 minute for index.html</li>
  <li>10 minute for /config.json</li>
  <li>365 days for hashed resources (ex /app-e12b3bbb7e5a572f270d.min.js)</li>
</ul>
Since index.html links to hashed resource names, any change to it will orphan old resources.
That's why hashed resource age can be 365 days.
zipkin-server/src/main/java/zipkin2/server/internal/ui/ZipkinUiConfiguration.java