Spring Boot with redirecting with single page angular2
If you're tired of trying to solve this problem by following so many conflicting solutions - look here!!
After hours upon hours trying to follow all the scattered advice from dozens of stack overflow and blog posts, I've finally found the minimum PURE spring boot + angular 6 application to always redirect to index.html after a refresh on a non-root page WHILE maintaining all your REST API
endpoint paths. No @EnableWebMvc
, no @ControllerAdvice
, no changes to application.properties
, no custom ResourceHandlerRegistry
modifications, just simplicity:
Very important pre-requisite
You *must* include the output of ng build
into Spring's resources/static
folder. You can accomplish this via the maven-resources-plugin
. Learn here: Copying multiple resource directories to independent target directories with maven
Code
@Controller
@SpringBootApplication
public class MyApp implements ErrorController {
public static void main(String[] args) {
SpringApplication.run(MyApp.class, args);
}
private static final String PATH = "/error";
@RequestMapping(value = PATH)
public String error() {
return "forward:/index.html";
}
@Override
public String getErrorPath() {
return PATH;
}
}
Reasoning
- Including the output of ng-build into
resources/static
at build time allows spring view redirects ("forward:/index.html"
) to succeed. It seems spring cannot redirect to anything outside of the resources folder so if you're trying to access pages at the root of the site, it won't work. - With default functionality (i.e. no additions of
@EnableWebMvc
or changes toapplication.properties
) navigating to/
automatically serves the index.html (iff it was included in theresources/static
folder) so no need to make changes there. - With default functionality (as stated above), any error encountered in a spring boot app routes to
/error
and implementingErrorController
overrides that behavior to - you guessed it - route toindex.html
which allowsAngular
to take over the routing.
Remarks
- Don't settle for the
HashLocationStrategy
to get over this problem as it is not recommended by Angular: https://angular.io/guide/router#which-strategy-is-best
For every local REST request not starting with /api overwrite and redirect to default webapp/index.html. I plan to serve anything /api to the spring controllers.
Update 15/05/2017
Let me re-phrase your query for other readers. (Correct me, if misunderstood)
Background
Using Spring Boot and Serving static resources from classpath
Requirement
All 404
non api requests should be redirected to index.html
.
NON API - means Requests in which URL doesn't start with /api
.
API - 404 should throw 404
as usual.
Sample Response/api/something
- will throw 404
/index.html
- will server index.html/something
- will redirect to index.html
My Solution
Let the Spring MVC throw exceptions, if any handler is not available for the given resource.
Add following to application.properties
spring.mvc.throw-exception-if-no-handler-found=true
spring.resources.add-mappings=false
Add a ControllerAdvice
as follows
@ControllerAdvice
public class RedirectOnResourceNotFoundException {
@ExceptionHandler(value = NoHandlerFoundException.class)
public Object handleStaticResourceNotFound(final NoHandlerFoundException ex, HttpServletRequest req, RedirectAttributes redirectAttributes) {
if (req.getRequestURI().startsWith("/api"))
return this.getApiResourceNotFoundBody(ex, req);
else {
redirectAttributes.addFlashAttribute("errorMessage", "My Custom error message");
return "redirect:/index.html";
}
}
private ResponseEntity<String> getApiResourceNotFoundBody(NoHandlerFoundException ex, HttpServletRequest req) {
return new ResponseEntity<>("Not Found !!", HttpStatus.NOT_FOUND);
}
}
You can customize the error message as you like.
Is there a way to prefix all controllers with api so that I do not have to write api every time.
For this, you can create a BaseController
and set the RequestMapping path to /api
Example
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RequestMapping("/api")
public abstract class BaseController {}
And extend this BaseController
and make sure you do not annotate child class with @RequestMapping
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class FirstTestController extends BaseController {
@RequestMapping(path = "/something")
public String sayHello() {
return "Hello World !!";
}
}
Previous Answer
You can create a Filter
which redirects to /index.html
if request path doesn't startsWith /api
.
// CODE REMOVED. Check Edit History If you want.
Try this instead
@SpringBootApplication
@Controller
class YourSpringBootApp {
// Match everything without a suffix (so not a static resource)
@RequestMapping(value = "/**/{path:[^.]*}")
public String redirect() {
// Forward to home page so that route is preserved.(i.e forward:/intex.html)
return "forward:/";
}
}