Docs

Documentation versions (currently viewingVaadin 24)

Flow-Hilla Hybrid Applications

Create hybrid applications by combining Hilla or React views with Flow views.

Hilla, which is part of Vaadin, is used for building Reactive web applications on Java backends. It integrates seamlessly a React TypeScript frontend with a Spring Boot backend.

You can develop hybrid applications that leverage Vaadin Flow and Hilla features. This allows you to combine in one application, Vaadin Flow routes written in pure Java with the Hilla ones written in React. This page shows how to add Hilla to an existing Vaadin Flow application. And it includes the reverse: how to add Vaadin Flow to an existing Hilla application.

See the source code for a complete Vaadin Flow-Hilla application.

Add Hilla to Flow Applications

To add Hilla to a Vaadin Flow application, you could start with a Spring Boot-based Vaadin Flow application (e.g., skeleton-starter-flow-spring). You would add Hilla to the project using the steps described in the sub-sections here.

Project dependency adjustments aren’t needed since Hilla is included in Vaadin.

Add React Views

Add a view file, counter.tsx, to the src/main/frontend/views sub-directory. It’ll be accessible under the path, /counter in a browser. Here’s an example of how that might look:

import React, { useState } from 'react';
import { Button } from '@vaadin/react-components/Button.js';
import { HorizontalLayout } from '@vaadin/react-components/HorizontalLayout.js';

export default function Counter() {
  const [counter, setCounter] = useState(0);

  return (
    <HorizontalLayout theme="spacing" style={{ alignItems: 'baseline' }}>
      <Button onClick={() => setCounter(counter + 1)}>Button</Button>
      <p>Clicked {counter} times</p>
    </HorizontalLayout>
  );
}

The directory, src/main/frontend/views is a default location where Vaadin looks for frontend views and configures React Router, based on the file’s structure.

Use Side Navigation or Anchor components to navigate from a Flow view to a Hilla view:

Anchor navigateToHilla = new Anchor("counter", "Navigate to a Hilla view");

Run the Application

Run the application using mvn spring-boot:run. Then open http://localhost:8080 in your browser.

Once you add a frontend view, Vaadin starts the Vite development server on the next application run, enabling frontend hot deployment.

Add Flow to Hilla Applications

If you already have a Hilla application, you can add Vaadin Flow to it. For example, starting from the Hilla project starter), you can add Vaadin Flow to the project using the steps in the sub-sections that follow.

Vaadin includes Hilla dependencies, so no dependency adjustment is needed.

Add Server-Side Routes

Add a view file, HelloView.java, to the src/main/java/org/vaadin/example/HelloView.java sub-directory. It’ll be accessible under the path, /hello in a browser. Here’s an example of that:

import com.vaadin.flow.component.button.Button;
import com.vaadin.flow.component.html.Paragraph;
import com.vaadin.flow.component.orderedlayout.VerticalLayout;
import com.vaadin.flow.component.textfield.TextField;
import com.vaadin.flow.router.Route;

@Route("hello")
public class HelloView extends VerticalLayout {
    public HelloView() {
        TextField textField = new TextField("Your name");
        Button button = new Button("Say hello", e ->
                add(new Paragraph("Hello, " + textField.getValue())));
        add(textField, button);
    }
}

Use Vaadin’s Side Navigation or React’s NavLink / Link components to navigate from a Hilla view to a Flow view:

import { NavLink } from 'react-router-dom';

<NavLink to="/flow-route">Navigate to a Flow View</NavLink>

Include Route to Hilla Main Menu

When using Hilla’s createMenuItems() utility function to build the main menu in the main layout, use the @Menu annotation to include the server route in the returned menu items. This is described in Creating Menu from Routes.

The @Menu annotation should always be used together with the @Route annotation as in the following example:

import com.vaadin.flow.component.orderedlayout.VerticalLayout;
import com.vaadin.flow.router.Menu;
import com.vaadin.flow.router.Route;

@Menu
@Route("hello")
public class HelloView extends VerticalLayout {
    public HelloView() {
    }
}

The @Menu` annotation has a title, an order, and an icon attribute. Values are passed to the client side and are available when using the createMenuItems() utility function to build the menu.

With access-controlled routes, it’s important not to send any information about them — other than what the user has permission to see. To send a route to the client side, annotate it with @Menu. The route must be accessible by Navigation Access Control.

The value of MenuAccessControl#getPopulateClientSideMenu determines the accessibility of the route:

  • AUTOMATIC: Accessible route is sent to the client. This is the default.

  • ALWAYS: Always send accessible route.

  • NEVER: Never send a route.

Only routes sent to the client can be shown in the main menu. In AUTOMATIC and ALWAYS modes, routes that reach the client are also filtered to include only received server routes if the root main layout exists when using File Based Routing. It checks the existence of the main layout file in src/main/frontend/views/ (e.g., src/main/frontend/views/@layout.tsx).

Mode is configurable with MenuAccessControl interface with the PopulateClientMenu enumerated list.

The following example changes the default mode to NEVER in a Spring Framework application:

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;
import com.vaadin.flow.server.auth.DefaultMenuAccessControl;
import com.vaadin.flow.server.auth.MenuAccessControl;

@SpringBootApplication
public class Application {

    public static void main(String[] args) {
        SpringApplication.run(Application.class, args);
    }

    @Bean
    public MenuAccessControl customMenuAccessControl() {
        DefaultMenuAccessControl menuAccessControl = new DefaultMenuAccessControl();
        menuAccessControl.setPopulateClientSideMenu(
                MenuAccessControl.PopulateClientMenu.NEVER);
        return menuAccessControl;
    }
}

This next example changes the default mode to NEVER in a non-Spring application by using Servlet Initialization Parameters menu.access.control with value org.vaadin.example.CustomMenuAccessControl. DefaultMenuAccessControl implements MenuAccessControl:

import com.vaadin.flow.server.auth.DefaultMenuAccessControl;

public class CustomMenuAccessControl extends DefaultMenuAccessControl {

    public CustomMenuAccessControl() {
        setPopulateClientSideMenu(PopulateClientMenu.NEVER);
    }
}

Flow Page Title in Hilla Main Menu

As described in Updating Page Title during Navigation, the page title for a route can be updated with an annotation and with an interface. The page title can be visible anywhere in the Hilla main menu by using Signal: window.Vaadin.documentTitleSignal. As long as the signal is initialized on the client side, the server keeps the signal’s value synchronized.

The following example illustrates how to use window.Vaadin.documentTitleSignal to show a page title defined with the PageTitle annotation in a server-side route in the Hilla main menu. This example includes only the relevant parts that need to be added for the functionality:

import { createMenuItems, useViewConfig } from '@vaadin/hilla-file-router/runtime.js';
import { effect, Signal, signal } from '@vaadin/hilla-react-signals';

// define Signal<string> type for the window.Vaadin
const vaadin = window.Vaadin as {
    documentTitleSignal: Signal<string>;
};
// initialize signal with empty string
vaadin.documentTitleSignal = signal('');
// keep document title in sync with the signal
effect(() =>  document.title = vaadin.documentTitleSignal.value);

export default function Layout() {
    ...
    // set signal value from the active view config
    vaadin.documentTitleSignal.value = useViewConfig()?.title ?? '';
    ...
    return (
        <AppLayout primarySection="drawer">
            ...
            <h2 slot="navbar" className="text-l m-0">
                {vaadin.documentTitleSignal}
            </h2>
            ...
        </AppLayout>
    );
}

Flow Server Side Layout for Hilla Views

It’s possible to use a Flow server side main layout for both server views and Hilla client views. The server view needs to implement RouterLayout and be annotated with @Layout.

@Layout
public class MainView extends Div implements RouterLayout {
    // Implementation omitted
}

For more information on RouterLayout, see Router Layouts & Nested Router Targets.

Information on dynamic menu item generation, see Menu Configuration.

Note
If the application is using access protection add @AnonymousAllowed on the MainView so that the request is not denied.

Hilla views will automatically use the server side layout if a matching one exists, and no hilla layout is available for the view.

To have a Hilla view ignore the server side layout ViewConfig contains the flowLayout value that if set to false will ignore automatic server layout.

export const config: ViewConfig = {
    flowLayout: false
};

export default function ClientView() {
  return (
    <HorizontalLayout theme="spacing" style={{ alignItems: 'baseline' }}>
      <p>Client layout without server side main layout</p>
    </HorizontalLayout>
  );
}
Note
Only one layout type (Hilla react or Flow) is supported at one time.

9da82521-5074-42b6-82a5-88fc207987d0