Documentation

Documentation versions (currently viewingVaadin 23)
Check out the new styling guides

Modifying the Bootstrap Page at Runtime

How to customize the application shell, or bootstrap page, to improve page loading speed

Application Shell

The App Shell Model aims to make your website faster by loading the 'important' parts of the web page first, so improving the user experience. The key to this is to deliver the minimum HTML, CSS and JavaScript required to display the user interface on the first visit, and eventually cache it for use on subsequent revisits.

The Application Shell in Vaadin is also known as the Bootstrap Page, or simply index.html.

Modifying the Application Shell

In Vaadin 15 and later, the developer has full control of the contents of index.html. There are various ways to modify it:

  1. On the client side, by editing frontend/index.html when the content is static, for instance the <viewport> tag.

  2. On the server side, for changes that require some dynamic server content, or simply when Java syntax is preferred; for example, making the application installable by enabling the @PWA built-in feature.

    • Implement AppShellConfigurator for cases covered by the AppShellSettings API, or by annotations.

    • Configure an IndexHtmlRequestListener for advanced cases that modify the document structure.

Customizing the Application Shell Template

The Vaadin servlet uses the frontend/index.html file as a template to generate the bootstrap page response. The servlet processes the template and injects the following information:

  • <base href='./relative/to/root'>: Vaadin calculates the relative path from the current request path to the root path of the application. That is required for relative links in view templates to work correctly.

  • Bundled script: Vaadin automatically adds the bundled and optimized script generated from the frontend/index.ts file. It uses a pre-configured Vite instance that is included together with vaadin-maven-plugin as a module bundler. Therefore, the frontend/index.html template does not need to include the index.ts (or index.js) script explicitly.

Tip
You can change the frontend directory path
The frontend directory path can be changed by providing the property vaadin.frontend.frontend.folder when running the Maven goals prepare-frontend or build-frontend from vaadin-maven-plugin.

Default Bootstrap Template and Entry Point

If the index.html or index.ts files in the frontend folder are missing, vaadin-maven-plugin generates a default corresponding file in the target folder. In that case the application uses server-side routing only. You can take control of these files by moving them into the frontend folder. By default these files look similar to the following:

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8" />
  <meta name="viewport" content="width=device-width, initial-scale=1" />
  <style>
    body, #outlet {
      height: 100vh;
      width: 100vw;
      margin: 0;
    }
  </style>
  <!-- index.ts is included here automatically (either by the dev server or during the build) -->
</head>
<body>
  <!-- vaadin-router in index.ts needs an outlet for displaying the views -->
  <div id="outlet"></div>
</body>
</html>
// import Vaadin client-router to handle client-side and server-side navigation
import { Router } from '@vaadin/router';

// import Flow module to enable navigation to Vaadin server-side views
import { Flow } from '@vaadin/flow-frontend';

const {serverSideRoutes} = new Flow({
  imports: () => import('../target/frontend/generated-flow-imports')
});

const routes = [
  // for client-side, place routes below (more info https://hilla.dev/docs/routing)

  // for server-side, the next magic line sends all unmatched routes:
  ...serverSideRoutes // IMPORTANT: this must be the last entry in the array
];

// Vaadin router needs an outlet in the index.html page to display views
export const router = new Router(document.querySelector('#outlet'));
router.setRoutes(routes);

This is the best place to customize the application shell, for example to put an analytics tag in the page.

...
<head>
  <title>My App</title>
</head>
<body>
  ...
  <!-- Google Analytics -->
  <script>
    (function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){
    (i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o),
    m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m)
    })(window,document,'script','https://www.google-analytics.com/analytics.js','ga');

    ga('create', 'UA-XXXXX-Y', 'auto');
    ga('send', 'my-app-bootstrap');
  </script>
</body>

Customizing the Application Shell at Runtime

The AppShellConfigurator Interface

In Java code, when adding dynamic content during the bootstrap process, use the AppShellConfigurator marker interface rather than editing the index.html.

Note
There must be a single application shell for the entire Vaadin application, so that no more than one class can implement AppShellConfigurator.
Note
AppShellConfigurator replaces the obsolete PageConfigurator interface.

The AppShellConfigurator.configurePage() Method

Override configurePage() to add content to the index.html template by calling the following AppShellSettings methods:

  • AppShellSettings.setViewport() to set the viewport value; this replaces the viewport present in the index.html template.

  • AppShellSettings.setPageTitle() to set the initial page title; this replaces the template title tag.

  • AppShellSettings.setBodySize() to configure the body width and height values.

  • AppShellSettings.addMetaTag() to append meta tags to the head.

  • AppShellSettings.addInlineFromFile() to include content from resource files.

  • AppShellSettings.addInlineWithContents() to add arbitrary content.

  • AppShellSettings.addLink() to add links to the head.

  • AppShellSettings.addFavIcon() to configure the favicon.

  • AppShellSettings.getLoadingIndicatorConfiguration() to configure a loading indicator when legacy bootstrapping is used (deprecated; see the details after the code example).

  • AppShellSettings.getReconnectDialogConfiguration() to configure the reconnect dialog when legacy bootstrapping is used (deprecated; see the details after the code example).

  • AppShellSettings.getPushConfiguration() to customize the push mechanism when legacy bootstrapping is used (deprecated; see the details after the code example).

public class AppShell implements AppShellConfigurator {

  @Override
  public void configurePage(AppShellSettings settings) {
    settings.setViewport("width=device-width, initial-scale=1");
    settings.setPageTitle("A cool vaadin app");
    settings.setBodySize("100vw", "100vh");
    settings.addMetaTag("author", "bunny");
    settings.addFavIcon("icon", "icons/icon-192.png", "192x192");
    settings.addLink("shortcut icon", "icons/favicon.ico");

    settings.addInlineFromFile(
            TargetElement.BODY,
            Position.APPEND,
            "custom.html",
            Wrapping.AUTOMATIC);
    settings.addInlineWithContents(Position.PREPEND,
            "console.log(\"foo\");", Wrapping.JAVASCRIPT);
  }
}
public class ServiceListener implements VaadinServiceInitListener{

    @Override
    public void serviceInit(ServiceInitEvent event) {
        event.getSource().addUIInitListener(uiInitEvent -> {
            LoadingIndicatorConfiguration indicator = uiInitEvent.getUI().getLoadingIndicatorConfiguration();
            indicator.setApplyDefaultTheme(false);
            indicator.setSecondDelay(700000);

            PushConfiguration push = uiInitEvent.getUI().getPushConfiguration();
            push.setPushMode(PushMode.AUTOMATIC);
            
            ReconnectDialogConfiguration dialog = uiInitEvent.getUI().getReconnectDialogConfiguration();
            dialog.setDialogText("reconnecting...");
        });
    }
}

Java Annotations

Vaadin provides a set of annotations to modify the application shell. However, in Vaadin 15, unlike in previous versions, these annotations must be placed in the application shell class.

  • @Viewport to set the viewport value.

  • @PageTitle to set the initial page title.

  • @BodySize to configure the body size.

  • @Meta to append meta tags to the head.

  • @Inline to include content from resource files in the index.html.

  • @PWA to define application PWA properties.

  • @Push to configure server push.

@Viewport("width=device-width, initial-scale=1")
@PageTitle("A cool vaadin app")
@BodySize(height = "100vh", width = "100vw")
@Meta(name = "author", content = "bunny")
@Inline(wrapping = Wrapping.AUTOMATIC,
        position = Position.APPEND,
        target = TargetElement.BODY,
        value = "custom.html")
@PWA(name = "Cool Vaadin App", shortName = "my-app")
@Push(value = PushMode.MANUAL, transport = Transport.WEBSOCKET)
public class AppShell implements AppShellConfigurator {
}
Note
Modifications in AppShellConfigurator.configurePage() have priority over the equivalent annotations.
Note
Annotations do not cover all the cases that can be achieved when overriding the AppShellConfigurator.configurePage() method.

The IndexHtmlRequestListener Interface

In addition, for advanced cases not covered in the previous section, content can be modified via an IndexHtmlRequestListener.

An implementation of the listener should be added via a ServiceInitEvent when a VaadinService is initialized. Check the ServiceInitListener tutorial for details of using Vaadin ServiceInitListeners.

The following example changes the body class dynamically:

public class MyIndexHtmlRequestListener implements
        IndexHtmlRequestListener {

    @Override
    public void modifyIndexHtmlResponse(
            IndexHtmlResponse indexHtmlResponse) {

        Document document = indexHtmlResponse.getDocument();
        Element body = document.body();
        body.classNames(computeBodyClassNames());
    }

    private Set<String> computeBodyClassNames() {
        // Introduce some logic to dynamically change the body class
        return Collections.singleton("my-className");
    }
}

This can also be achieved using a servlet container deployment property with the name useDeprecatedV14Bootstrapping. Note, however, that this option is only supported if webpack is used as the frontend build tool and not if the application uses Vite (which is the default). webpack can be enabled using its associated feature flag. Learn more about how to enable it here.

38A2B3F1-CC6B-45DF-8CB8-9DEF23BA53B0