> Markdown version of [Modifying Bootstrap Page at Runtime](https://vaadin.com/docs/latest/flow/advanced/modifying-the-bootstrap-page). Section index: [llms.txt](https://vaadin.com/docs/latest/flow/llms.txt)

# Modifying Bootstrap Page at Runtime

The [application shell model](https://developers.google.com/web/fundamentals/architecture/app-shell) aims to make websites faster by loading the *important* parts of web pages first to improve 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 to cache it for use on subsequent visits.

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

## <a id="modifying-the-application-shell"></a>Modifying the Application Shell

The developer has full control of the contents of `index.html`. You can modify it in various ways.

On the client side, you can take control of `index.html` when the content is static, for instance the `<viewport>` tag. Copy the generated file into the `frontend` folder rather than writing one from scratch, as described in [Default Template & Entry Point](#default-bootstrap-template-and-entry-point). On the server side, you can make changes that require dynamic server content, or when Java syntax is preferred. An example of would be making the application installable by enabling the `@PWA` built-in feature.

You can implement `AppShellConfigurator` for cases covered by the `AppShellSettings` API, or by annotations. And you can configure an `IndexHtmlRequestListener` for advanced cases to modify the document structure.

## <a id="application-shell-template"></a>Application Shell Template

The Vaadin servlet uses an `index.html` file as a template to generate the bootstrap page response: the one in the `frontend` folder where the project has one, and the generated default otherwise. 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. This 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.tsx` file. It uses a pre-configured [Vite](https://vitejs.dev/) instance that’s included with `vaadin-maven-plugin` as a module bundler. Therefore, the `index.html` template doesn’t need to include explicitly the `index.tsx` script — or the `index.ts` or `index.js` script.

> **Tip:** The `frontend` directory path can be changed with the `frontendDirectory` parameter of `vaadin-maven-plugin`, which the `prepare-frontend` and `build-frontend` goals use. The build records the path so that the application can read it at runtime from the `vaadin.frontend.folder` property. See [Plugin Configuration Options](https://vaadin.com/docs/latest/flow/configuration/maven.md#properties) for details.

### <a id="default-bootstrap-template-and-entry-point"></a>Default Template & Entry Point

Where the `index.html` or `index.tsx` file is missing from the frontend folder, the build generates a default into `frontend/generated/`, so neither file has to exist in the project. For `index.html`, that location is new in Vaadin 25.3 (new in undefined): earlier versions generated the file into the `frontend` folder itself. Without an `index.tsx`, the application uses only server-side routing. To take control of one of these files, copy it from `frontend/generated/` into the `frontend` folder, where it replaces the generated default. By default, these files look similar to the following:

Default `index.html`

```html
<!DOCTYPE html>
<html>
<head>
  <meta charset="UTF-8" />
  <meta name="viewport" content="width=device-width, initial-scale=1, viewport-fit=cover" />
  <style>
    html, body, #outlet {
      height: 100%;
      width: 100%;
      margin: 0;
    }
  </style>
  <!-- index.ts is included here automatically (either by the dev server or during the build) -->
</head>
<body>
  <!-- This outlet div is where the views are rendered -->
  <div id="outlet"></div>
</body>
</html>
```

Default `index.tsx`

```tsx
import { createElement } from 'react';
import { createRoot } from 'react-dom/client';
import { RouterProvider } from 'react-router';
import { router } from 'Frontend/generated/routes.js'; // (1)

function App() {
  return <RouterProvider router={router} />;
}

const outlet = document.getElementById('outlet')!; // (2)
const root = createRoot(outlet);
root.render(createElement(App));
```

1. The routes themselves are configured in `routes.tsx`, which registers the server-side (i.e., Flow) routes and any client-side React routes. If you add your own `frontend/routes.tsx` file, the import points to that file instead of the generated one.

2. The `outlet` element is the one declared in `index.html`; it’s where the router renders the views.

For more about configuring the routes, see the [React Router Reference](https://vaadin.com/docs/latest/hilla/reference/react-router.md).

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

index.html

```xml
...
<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>
```

## <a id="customizing-application-shell-at-runtime"></a>Customizing Application Shell at Runtime

You may use a few methods to customize an application shell at runtime. They’re described in the following sub-sections.

### <a id="application-shell-configurator"></a>`AppShellConfigurator` Interface

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

There must be a single application shell for the entire Vaadin application, so that no more than one class can implement `AppShellConfigurator`. In a multi-module application, the class implementing `AppShellConfigurator` can’t be in the dependent JAR file of the JAR or WAR file which starts the application.

> **Note:** `AppShellConfigurator` replaces the obsolete `PageConfigurator` interface.

#### <a id="appshellconfigurator-configurepage-method"></a>`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.

```java
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);
  }
}
```

`ServiceListener.java`

```java
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...");
        });
    }
}
```

#### <a id="using-an-svg-favicon"></a>Using an SVG Favicon

Modern browsers support scalable SVG favicons. Place the SVG file in a folder served as static resources, for example `src/main/resources/META-INF/resources/icons/favicon.svg`, and add it with `addFavIcon()`. Use `any` as the size, since a vector image scales to any size:

```java
@Override
public void configurePage(AppShellSettings settings) {
  settings.addFavIcon("icon", "icons/favicon.svg", "any");
}
```

Browsers detect the image format from the `Content-Type` of the response. To declare the type explicitly in the link element — `type="image/svg+xml"` — use the `addLink()` overload that takes a map of attributes:

```java
@Override
public void configurePage(AppShellSettings settings) {
  settings.addLink("icons/favicon.svg",
          Map.of("rel", "icon", "type", "image/svg+xml"));
}
```

Not all browsers support SVG favicons. To provide a fallback for them, add a PNG or ICO favicon as well.

#### <a id="java-annotations"></a>Java Annotations

Vaadin provides a set of annotations to modify the application shell.

- `@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.

```java
@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 {
}
```

Modifications in `AppShellConfigurator.configurePage()` have priority over the equivalent annotations. Annotations don’t cover all of the cases that can be achieved when overriding the `AppShellConfigurator.configurePage()` method.

#### <a id="forcing-a-page-reload-after-an-invalid-server-response"></a>Forcing a Page Reload After an Invalid Server Response

If the XHR response can’t be parsed as JSON, Vaadin looks for a “Vaadin-Refresh” string anywhere inside the response text. If it’s present, Vaadin reloads the page instead of showing an error message. Usually such responses are served by some 3rd party servers and then you need to add the refresh token as a meta tag to the HTML page served by it.

Sample HTML response with the meta tag

```xml
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width">
  <meta name="refresh" content="Vaadin-Refresh">
  <title>Vaadin App</title>
</head>
<body>
  <h1>Invalid server response</h1>
</body>
</html>
```

But sometimes due to proxy or firewall timeout, a new session is served for the client, without the client being aware of that. In such cases you need to add the refresh token in Vaadin bootstrap page.

```java
public class AppShell implements AppShellConfigurator {

  @Override
  public void configurePage(AppShellSettings settings) {
    settings.addMetaTag("refresh", "Vaadin-Refresh");
  }
}
```

### <a id="IndexHtmlRequestListener-interface"></a>`IndexHtmlRequestListener` Interface

For advanced cases not covered in the previous section, content can be modified via an `IndexHtmlRequestListener`.

An implementation of the listener should be added with a `ServiceInitEvent` when a `VaadinService` is initialized. See the [ServiceInitListener tutorial](https://vaadin.com/docs/latest/flow/advanced/service-init-listener.md) for details of using Vaadin `ServiceInitListeners`.

The following example changes the body class, dynamically:

```java
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 that this option, however, is only supported if `webpack` is used as the frontend build tool and not if the application uses Vite — which is the default. You can enable `webpack` by using its associated feature flag. Learn more about how to enable it on the [Hot Deploy & Live Reload](https://vaadin.com/docs/latest/flow/configuration/live-reload.md#webpack-feature-flag) documentation page.

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