Using Vaadin Flow’s @Route for Lazy‑Loaded, Spring‑Backed Navigation
Learn how to declare Vaadin views with @Route, load them lazily, inject Spring beans, and guard navigation while keeping the initial bundle small.
01 Mar 2026, 03:11 UTC

Problem: Navigation feels heavy and slows first load
When building a Vaadin Flow application with Spring Boot, every view you add contributes to the initial JavaScript bundle. Users see a longer wait before they can interact with the UI, and the UI classes are instantiated on the server for each session, which can increase memory usage under load.
Solution: Use @Route with lazy loading and Spring beans
Vaadin Flow’s @Route annotation lets you map a URL pattern to a View class. By placing each view in its own module or using @RouteAlias, the framework generates a separate client‑side chunk that is fetched only when the route is first visited. Because each routed view can be a Spring bean (@Component), you can inject services, repositories, or scoped beans via constructor injection, keeping UI logic testable and decoupled from the Vaadin framework. Navigation guards such as implementing BeforeEnterObserver or annotating with @AllowAnonymous centralize authentication checks.
Worked example: Two views with lazy loading
- Create the project – Use start.vaadin.com to generate a Vaadin Spring Boot starter with the dependencies
vaadinandspring-boot-starter-web. Unzip and open the folder in your IDE. - Add Maven/Gradle – No extra dependencies are needed; the starter already includes Vaadin Flow and Spring Web.
- Main view –
src/main/java/com/example/demo/MainView.javapackage com.example.demo; import com.vaadin.flow.component.button.Button; import com.vaadin.flow.component.orderedlayout.VerticalLayout; import com.vaadin.flow.router.Route; import com.vaadin.flow.router.BeforeEnterObserver; import com.vaadin.flow.router.BeforeEnterEvent; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; @Component @Route("") public class MainView extends VerticalLayout implements BeforeEnterObserver { private final GreetingService greetingService; @Autowired public MainView(GreetingService greetingService) { this.greetingService = greetingService; setSizeFull(); setJustifyContentMode(JustifyContentMode.CENTER); setAlignItems(Alignment.CENTER); Button detail = new Button("Go to Detail", e -> getUI().ifPresent(ui -> ui.navigate("detail"))); add(detail); } @Override public void beforeEnter(BeforeEnterEvent event) { // Example guard: redirect if service says user not allowed if (!greetingService.isAllowed()) { event.rerouteTo("error"); } } } - Detail view (lazy loaded) –
src/main/java/com/example/demo/DetailView.javapackage com.example.demo; import com.vaadin.flow.component.button.Button; import com.vaadin.flow.component.orderedlayout.VerticalLayout; import com.vaadin.flow.router.Route; import org.springframework.stereotype.Component; @Component @Route("detail") public class DetailView extends VerticalLayout { public DetailView() { setSizeFull(); setJustifyContentMode(JustifyContentMode.CENTER); setAlignItems(Alignment.CENTER); Button back = new Button("Back to Main", e -> getUI().ifPresent(ui -> ui.navigate(""))); add(back); } } - Sample service –
src/main/java/com/example/demo/GreetingService.javapackage com.example.demo; import org.springframework.context.annotation.Scope; import org.springframework.stereotype.Service; import java.util.Random; @Service @Scope("prototype") public class GreetingService { private final Random rand = new Random(); public boolean isAllowed() { // Simulate a simple check return rand.nextBoolean(); } } - Run the application – From the project root, execute:
The process needs read/write access to the project directory; no special privileges are required../mvnw spring-boot:run # Unix/macOS mvnw.cmd spring-boot:run # Windows - Verify lazy loading – Open the app in Chrome, open DevTools → Network, enable “Disable cache”, reload the page, then click the “Go to Detail” button. You should see a separate request for a chunk named something like
detail‑…jsafter the initial load, confirming that the DetailView bundle is fetched on demand. - Check navigation without full reload – Observe that the URL changes from
http://localhost:8080/tohttp://localhost:8080/detailand the page content swaps without a browser reload indicator.
Trade‑off: Server‑side state and SEO
Each user session holds the UI component tree in memory. With many concurrent users, the heap requirement grows linearly; consider enabling sticky‑session clustering or increasing the JVM heap if you expect high traffic. Additionally, the initial HTML sent to the browser is a minimal shell; crawlers that do not execute JavaScript will not see the view content. For public‑facing pages that need SEO, combine Vaadin with a pre‑rendering add‑on or use server‑side rendering (SSR) for those routes.
Actionable checklist
- Define each navigable screen as a
@Routeclass. - Place the class in its own Java file (or separate module) to enable lazy chunk generation.
- Annotate the class with
@Componentand inject required Spring beans via constructor. - Implement
BeforeEnterObserveror use@AllowAnonymousfor route‑level guards. - Run
./mvnw spring-boot:runand verify lazy loading in the Network tab. - Monitor session memory usage (e.g., via
jstator a monitoring tool) and plan for clustering if needed. - For SEO‑critical routes, evaluate a pre‑rendering solution or SSR.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.