Flow: Stylesheet is not found after UI.navigate to AppLayout in production mode

Created on 11 May 2020  ยท  12Comments  ยท  Source: vaadin/flow

Description of the bug

The stylesheet is not loaded properly on UI.navigate for vaadin-app-layout in production mode. Not occurring in dev mode.
See: https://github.com/appreciated/vaadin-app-layout/issues/297, also https://github.com/appreciated/vaadin-app-layout/issues/306 and https://github.com/vaadin/flow-spring-examples/issues/53

Minimal reproducible example

Navigate with UI.navigate to an AppLayout view, e.g. from a login screen.

Expected behavior

Correctly loaded stylesheet.

Actual behavior

Server can not find stylesheet, resulting in missing style until page reload.

Workaround

Replace UI.navigate(...) with UI.getCurrent().getPage().setLocation(...) (suggested in https://github.com/appreciated/vaadin-app-layout/issues/297)

Tested versions:

- Vaadin / Flow version: 14.2.0.beta1 / 2.2 (also tested with latest 14.1.x versions)
- Java version: OpenJDK-11
- OS version: Windows10 (1909) and Debian 10 (Buster)
- Browser version: Vivaldi (3.0.1874.32) based on Chromium (81.0.4044.123)
- IDE: IntelliJ 2020.1.1
High Major bug waiting for author

Most helpful comment

Sorry, for this specific issue after spending several days I was able to reproduce it.

One more time : thanks to @rolandoisidoro who helped with this.

All 12 comments

Same problem here. I've been using 14.1.x, have tried in all versions from 14.1.17 to 14.1.27. When forcing a page refresh the CSS gets loaded correctly.

On the first request, after a successful login, Vaadin tries to load the CSS from a frontend-es6 location instead of the correct frontend. Forcing a page refresh, the CSS file is loaded from the correct location.

When directly accessing the URL Vaadin is trying to load I get the following result with a 404 HTTP status:

Could not navigate to 'frontend-es6/css/main.css'

Reason: Couldn't find route for 'frontend-es6/css/main.css'

Unfortunately I'm not able to understand the issue from this mixture of incomplete/not specified information.

Navigate with UI.navigate to an AppLayout view, e.g. from a login screen.

How may I find the AppLayout view?
How my I find the login screen ?

Correctly loaded stylesheet.

Which stylesheet ?
A custom stylesheet or included in the component ?

frontend-es6 location is referenced in many tickets here.
This location is used in compatibility mode _ONLY_.
So do you run compatibility mode ?

Was not able to find any steps to reproduce.
Please note that "Minimal reproducible example" means:

  • provide the code which doesn't contain anything else except the necessary snippets which allow to reproduce the bug.
  • the code has to contain _all_ information which is required to reproduce the issue.

Since there are no steps to reproduce I've been trying to do the following:

Check the errors in the browser console.
I don't see any error.

public class AppLayoutView extends AppLayout {

}

@Route(value = "a", layout = AppLayoutView.class)
public class AppLayoutRoute extends Div {

    public AppLayoutRoute() {
        setText("Navigated");
    }

}

public class MainView extends VerticalLayout {

    public MainView() {
        GreetService greetService = new GreetService();
        Button button = new Button("Say hello", e -> 
            UI.getCurrent().navigate(AppLayoutRoute.class));

        add(button);
    }
}

@denis-anisimov , I'll try to add as much insight as possible. Given the following project file structure:

โ”œโ”€โ”€ src/main/java/org/vaadin/example/
โ”‚   โ”œโ”€โ”€ ui
โ”‚   โ”‚   โ”œโ”€โ”€ layout
โ”‚   โ”‚   โ”‚   โ””โ”€โ”€ MainLayout.java
โ”‚   โ”‚   โ”œโ”€โ”€ views
โ”‚   โ”‚   โ”‚   โ”œโ”€โ”€ anon
โ”‚   โ”‚   โ”‚   โ”‚   โ””โ”€โ”€ LoginView.java
โ”‚   โ”‚   โ”‚   โ”œโ”€โ”€ authc
โ”‚   โ”‚   โ”‚   โ”‚   โ”œโ”€โ”€ ...
โ”‚   โ”‚   โ”‚   โ”‚   โ””โ”€โ”€ MainView.java
โ”‚   โ”‚   โ”‚   โ””โ”€โ”€ ...
โ”‚   โ”‚   โ””โ”€โ”€ ...
โ”‚   โ””โ”€โ”€ ...
โ””โ”€โ”€ ...

LoginView.java

@PageTitle(LoginView.TITLE)
@Route(LoginView.URL)
@StyleSheet("css/login.css")
public class LoginView extends Div implements HasComponents, RouterLayout, PageConfigurator {

(...)

    LoginForm component = new LoginForm();
    component.addLoginListener(e -> {
            boolean isAuthenticated = authenticate(e);
            if (isAuthenticated) {
                    UI.getCurrent().navigate(MainView.class);
            } else {
                    component.setError(true);
            }

(...)

}

MainLayout.java

@StyleSheet("css/main.css")
public class MainLayout extends AppLayout implements RouterLayout, PageConfigurator, AfterNavigationObserver {

MainView.java

@PageTitle(MainView.TITLE)
@Route(value = MainView.URL, layout = MainLayout.class)
public class MainView extends VerticalLayout implements HasComponents, RouterLayout {

The problem occurs when I perform the following actions:

  1. Access the Login view;
  2. Login successfully.

After step 2, the main.css file isn't loaded upon the LoginView -> MainView redirect, as described before:

On the first request, after a successful login, Vaadin tries to load the CSS from a frontend-es6 location instead of the correct frontend. Forcing a page refresh, the CSS file is loaded from the correct location.

Workaround

At this point, I managed to make it work by replacing UI.getCurrent().navigate(MainView.class); to UI.getCurrent().getPage().setLocation(MainView.URL); , as suggested in https://github.com/appreciated/vaadin-app-layout/issues/297.

@rolandoisidoro , thanks for clarification, I will look whether I'm able to reproduce with these steps.

But still I would like to know the project configuration.
Is it 14.2 Platform ?
Do you use compatibility mode ? Because I see frontend-es6 mention.
In the latter case I would like to see the pom.xml.
Or better the project based on https://github.com/vaadin/skeleton-starter-flow/tree/v14
or https://github.com/vaadin/skeleton-starter-flow-spring.

I will use one of those projects _ANYWAY_ to reproduce.

@denis-anisimov , sorry for not providing that info earlier. In my first comment on this issue I mentioned:

I've been using 14.1.x, have tried in all versions from 14.1.17 to 14.1.27.

I'm not using compatibility mode, nor Spring. My setup relies on Vaadin + JEE. Here are some relevant parts of my pom.xml, which was based in some previous Vaadin starter project:

    <properties>

        (...)

        <vaadin.version>14.1.27</vaadin.version>

        (...)

    </properties>

    <pluginRepositories>
        <pluginRepository>
            <id>central</id>
            <url>https://repo1.maven.org/maven2/</url>
            <snapshots>
                <enabled>false</enabled>
            </snapshots>
        </pluginRepository>
    </pluginRepositories>

    <repositories>
        <repository>
            <id>central</id>
            <url>https://repo1.maven.org/maven2/</url>
            <snapshots>
                <enabled>false</enabled>
            </snapshots>
        </repository>

        <!-- Repository used by many Vaadin add-ons -->
        <repository>
            <id>Vaadin Directory</id>
            <url>https://maven.vaadin.com/vaadin-addons</url>
            <snapshots>
                <enabled>false</enabled>
            </snapshots>
        </repository>
    </repositories>

    <dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>com.vaadin</groupId>
                <artifactId>vaadin-bom</artifactId>
                <version>${vaadin.version}</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
        </dependencies>
    </dependencyManagement>

    <dependencies>
        <!-- Vaadin -->
        <dependency>
            <groupId>com.vaadin</groupId>
            <!-- vaadin-core (instead of vaadin) to use only free components -->
            <artifactId>vaadin</artifactId>
            <exclusions>
                <!-- Webjars are only needed when running in Vaadin 13 compatibility mode -->
                <exclusion>
                    <groupId>com.vaadin.webjar</groupId>
                    <artifactId>*</artifactId>
                </exclusion>
                <exclusion>
                    <groupId>org.webjars.bowergithub.insites</groupId>
                    <artifactId>*</artifactId>
                </exclusion>
                <exclusion>
                    <groupId>org.webjars.bowergithub.polymer</groupId>
                    <artifactId>*</artifactId>
                </exclusion>
                <exclusion>
                    <groupId>org.webjars.bowergithub.polymerelements</groupId>
                    <artifactId>*</artifactId>
                </exclusion>
                <exclusion>
                    <groupId>org.webjars.bowergithub.vaadin</groupId>
                    <artifactId>*</artifactId>
                </exclusion>
                <exclusion>
                    <groupId>org.webjars.bowergithub.webcomponents</groupId>
                    <artifactId>*</artifactId>
                </exclusion>
            </exclusions>
        </dependency>
        <dependency>
            <groupId>com.vaadin</groupId>
            <artifactId>vaadin-cdi</artifactId>
        </dependency>

        (...)

    </dependencies>

I tried to reproduce the problem and haven't seen that you use @StyleSheet instead of @CssImport.

A working example with @CssImport can be found at https://github.com/knoobie/vaadin-maven-multi-module-example.git branch flow/issue-8292.

I'm wondering if @rolandoisidoro problem could be fixed with using @CssImport instead of @StyleSheet. Not sure about the problem of @Dudeplayz.

Was there any reasoning to create different annotations @denis-anisimov? There is an explanation in the docs "In server-side views (Java), use the @CssImport annotation to import local/bundled style sheets and the @StyleSheet annotation to import external/linked style sheets". - dunno if there was more.

I think I've started to understand the issue.

First of all: @rolandoisidoro thank you very much for quick response with the necessary details.
The original ticket description doesn't allow to work with this ticket anyhow.

The second thing which is important here: AppLayout has no any relation to the issue.
There is so much not relevant information and almost no important information in the ticket.

The only thing which is important : custom style specified via @StyleSheet("css/login.css") doesn't work as expected in NPM mode.

And this is just because @StyleSheet should be normally used for external URLs.
For local/bundled styles @CssImport should be used.

From the javadocs of StyleSheet:
Relative URLs are interpreted as relative to the configured {@code frontend} directory location.

So when you use css/login.css it becomes "frontend://css/login.css".
In the dev mode it's resolved to /frontend/css/login.css.
But in production mode we still have a logic on the client side which uses frontend-es6 prefix to resolve the relative URLs.
I think we should fix this logic so that /frontend/ is still used in NPM mode.
This is what I'm going to check and fix.

The problem is : I don't see in the comments where css/login.css is located in the project.
Project structure shows only Java classes location.

Anyway : when you use NPM you should put all your resources into the /frontend folder of your root project. In this case you won't be able to use @StyleSheet at all because it won't be able to find this resource at all.

I assume that the location of css/login.css is /src/main/webapp/frontend/css/login.css .
And this location normally should not be used in NPM.
Another way can be : avoid frontend schema at all since it's not supposed to work in NPM at all.
Use context schema and set it explicitly in your @StyleSheet annotation.

Was there any reasoning to create different annotations @denis-anisimov?

I'm not sure.
Not mine decision.

But @StyleSheet is a mostly compatibility mode annotation which still works for GLOBAL styles in NPM mode as well.
But if you want to use styles for your web components then @StyleSheet can't be ever used.
@CssImport is the only correct way to use styles for web components.

@StyleSheet could have been reused for NPM mode but in fact @CssImport has a number of parameters which has no sense for @StyleSheet and semantically it's different. So I guess that was the reason.

It seems that it is not easy to reproduce this issue and there are lots of unrelated information here.

We've spent time on reproducing this issue and will not spend any more time. Please create a new ticket with the exact steps to reproduce the problem, thank you.

Sorry, for this specific issue after spending several days I was able to reproduce it.

One more time : thanks to @rolandoisidoro who helped with this.

@denis-anisimov , sorry for coming back late to the discussion. In my case, the decision to go with @Stylesheet over @CssImport was my call, taking into consideration what I read on the oficial documentation regarding Importing Style Sheets to the global scope:

External/linked style sheets can be used to import styles without inlining the contents to the application bundle. This allows the browser to load and cache the style sheet separately from the rest of the application.

My goal was to have linked style sheets files instead of inlining them.

I see you were able to reproduce the issue, can you clarify what are the conditions under which it occurs?

Use @Stylesheet with relative path or explicit frontend:// schema.
Make a route view which navigates via UI.navigate to the view above (with @Stylesheet).

It works fine in dev mode, it doesn't work in production mode.
If you load view with @Stylesheet directly (without programatic navigation) then it works fine.
It's necessary to use UI.navigate.

This is a consequence of applying URI resolution on the client side which uses compatibility mode URLs . Compatibility mode URLs should not be sent to the client side if app is running without compatibility mode.

Was this page helpful?
0 / 5 - 0 ratings

Related issues

pleku picture pleku  ยท  4Comments

anezthes picture anezthes  ยท  4Comments

rucko24 picture rucko24  ยท  3Comments

manolo picture manolo  ยท  3Comments

appreciated picture appreciated  ยท  3Comments