SPA with Spring Boot - serve index.html for non-API requests
Asked Answered
G

2

8

I'm currently working on a webpage that should use a single page React front-end. For the back-end, I'm using the spring boot framework.

All api calls shall use a url prefixed with /api and should be handled by the REST controllers.

All other urls should simply serve the index.html file. How would I achieve this with spring?

Gonzalez answered 18/8, 2016 at 11:8 Comment(0)
B
10

The easiest way to achieve what you want is to implement custom 404 handler.

Add these params to your application.properties:

spring.resources.add-mappings=false
spring.mvc.throw-exception-if-no-handler-found=true

First property removes all default static resource handling, second property disables Spring's default whitelabel page (by default Spring catches NoHandlerFoundException and serves standard whitelabel page)

Add 404 handler to your application context:

import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.servlet.NoHandlerFoundException;
import javax.servlet.http.HttpServletRequest;

@ControllerAdvice
public class PageNotFoundController {
    @ExceptionHandler(NoHandlerFoundException.class)
    public String handleError404() {
            return "redirect:/index.html";
    }
}

At the end you will need to add your custom view resolver for serving your static content (index.html in this case)

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.ViewResolver;
import org.springframework.web.servlet.config.annotation.EnableWebMvc;
import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter;
import org.springframework.web.servlet.view.InternalResourceView;
import org.springframework.web.servlet.view.UrlBasedViewResolver;

@Configuration
@EnableWebMvc
public class WebConfig extends WebMvcConfigurerAdapter {

    @Override
    public void addResourceHandlers(ResourceHandlerRegistry registry) {
        registry.addResourceHandler("/index.html").addResourceLocations("classpath:/static/index.html");
        super.addResourceHandlers(registry);
    }

    @Bean
    public ViewResolver viewResolver() {
        UrlBasedViewResolver viewResolver = new UrlBasedViewResolver();
        viewResolver.setViewClass(InternalResourceView.class);
        return viewResolver;
    }

}

Your index.html should be placed in /resources/static/ directory.

Babbler answered 18/8, 2016 at 21:29 Comment(1)
What if there's SpringSecurity present in the app ? In that case NoHandlerFoundException doesn't even get fired. Server just throws InsufficientAuthenticationException. And when I simply replaced the PageNotFoundController's @ExceptionHandler annotation with that exception, it didn't seem to work. Server returned a 401 Unauthorized white label page. Do you have a suggestion for that ?Ignition
R
1

Please make sure to include thymeleaf in pom.xml:

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>

Add these in your application.properties

spring.mvc.pathmatch.matching-strategy=ANT_PATH_MATCHER
spring.thymeleaf.check-template-location=true

The following is optional. You might need this, for example, if you want to change the path to store static files.

spring.web.resources.static-locations=classpath:/your/path/to/the/*folder*/that/has/index/file
spring.thymeleaf.prefix=classpath:/your/path/to/the/*folder*/that/has/index/file

Create your SpaController.java then write like this:

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;

@Controller
public class SpaController {
    @RequestMapping(value = {"/{path:^(?!api|public)[^\\.]*}", "/**/{path:^(?!api|public).*}/{path:[^\\.]*}"})
    public String get(){
        return "index";
    }
}

Or, in Kotlin:

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;

@Controller
public class SpaController {
    @RequestMapping(value = [
        "/{path:^(?!api|public)[^\\.]*}",
        "/**/{path:^(?!api|public).*}/{path:[^\\.]*}"
    ])
    public fun get() : String
    {
        return "index";
    }
}

But please note that this might not work for path like /***/***/*** (having slashes more than 2). If you need that case, you need to add or modify the matcher.

See also: Spring catch all route for index.html

Resolvable answered 14/1, 2023 at 14:12 Comment(1)
maybe it is better to use nginx to redirect /api and everything else to index.htmlResolvable

© 2022 - 2024 — McMap. All rights reserved.