Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Serve React Router #464

Closed
Dominik-Robert opened this issue Apr 1, 2019 · 20 comments · Fixed by #493
Closed

Serve React Router #464

Dominik-Robert opened this issue Apr 1, 2019 · 20 comments · Fixed by #493

Comments

@Dominik-Robert
Copy link

Go Version: 1.12.

Mux-Version: latest (not important)

Iam able to serve a react app which I created with "create-react-app", but how I serve an app which is created with "create-react-app" and has react router in it? Is this possible?

@Dominik-Robert Dominik-Robert changed the title Serve React Router with React Router Serve React Router Apr 1, 2019
@elithrar
Copy link
Contributor

elithrar commented Apr 1, 2019 via email

@Dominik-Robert
Copy link
Author

Dominik-Robert commented Apr 1, 2019

I tried to serve the folder where the index.html is located, which worked without the react-router. So then I tried to give serve the static route (static/js) but that didn't worked. A little bit strange that it worked without react-router.

The routes from my golang are / and for my react-router it is / and /app /signIn. I tried many variations but nothing worked.

The go-Code looked like the Readme-Code AND like other examples in the internet for static files, which is strange, because the react app is build for production and of course a static file.

@elithrar
Copy link
Contributor

elithrar commented Apr 1, 2019 via email

@Dominik-Robert
Copy link
Author

Sure.

My Go-Code looks actually like this:

package main

import (
	"flag"
	"log"
	"net/http"
	"time"

	"github.com/gorilla/mux"
)

func main() {
	var dir string

	flag.StringVar(&dir, "dir", ".", "the directory to serve files from. Defaults to the current dir")
	flag.Parse()
	r := mux.NewRouter()

	// This will serve files under http://localhost:8000/static/<filename>
	r.PathPrefix("/").Handler(http.StripPrefix("/", http.FileServer(http.Dir(dir))))

	srv := &http.Server{
		Handler: r,
		Addr:    "127.0.0.1:8000",
		// Good practice: enforce timeouts for servers you create!
		WriteTimeout: 15 * time.Second,
		ReadTimeout:  15 * time.Second,
	}

	log.Fatal(srv.ListenAndServe())
}

dir is "frontend/build/" which is the folder, where the index.html from the react-framework is located.
I tried many other PathPrefix or with/without StripPrefix but nothing worked...

Think this can be important, so here is the App.js from react.

import React, { Component } from 'react';

import { BrowserRouter as Router, Route, Switch } from "react-router-dom";

import SignIn from './layouts/SignIn.js';
import Content from './layouts/Content.js';


class App extends Component {
  render() {
    return (
        <Router>
            <Switch>
            <Route path="/signIn" exact component={SignIn} />
            <Route path="/app/*" exact component={Content} />
            <Route path="/*" exact component={Content} />
            </Switch>
        </Router>
    );
  }
}

export default App;

@elithrar
Copy link
Contributor

elithrar commented Apr 2, 2019 via email

@Dominik-Robert
Copy link
Author

It shows a complete blank page or 404 page not found.

@elithrar
Copy link
Contributor

elithrar commented Apr 3, 2019

@Dominik-Robert - please make this easier for me to help you :)

  1. What URL do you visit?
  2. Does any URL work at all, or do they all 404?
  3. Does it only 404 when you access a route "controlled" by React Router?

I can't "run" your app without installing React / Create React App.

@elithrar
Copy link
Contributor

elithrar commented Apr 3, 2019

Note: Based on the assumption that everything 404's, it's highly likely you just have the wrong dir specified, and that your problems are unrelated to the React part.

	r.PathPrefix("/").Handler(http.StripPrefix("/", http.FileServer(http.Dir(dir))))

... will work if the index.html and JS files are co-located with your .go file - in the same directory - and you have relative paths. If you're running your Go program from elsewhere, or using go run in older versions of Go, it may not be running from the folder you think it is.

@Dominik-Robert
Copy link
Author

Oh excuse me I write now a little bit more:

The paths where the react files are located are in a subfolder frontend. When you build something with react it creates a folder which is called build. In this folder there is the index.html, but there are more files under static.

I call this with my previous posted code and get 404 not found on following paths:
/
/app
/signIn

Which are the exact path which I want to to look in my react app. I don't know why, but is it possible that the path is different then? So that / matches to the golang app and to the react app, but then it should be different to match it again to the react app. Or another idea, is it possible that the mux router uses exact matches from a router path, so that / is matched, but /app not.

When I make a file Handler are the files in a subfolder also served? So that the linked files in the react app are available?

Hope you understand my english :)

@elithrar
Copy link
Contributor

elithrar commented Apr 5, 2019

When I make a file Handler are the files in a subfolder also served? So that the linked files in the react app are available?

If the parent matches - e.g. if they are within that directory structure. Can you share your dir structure more clearly?

(BTW, I know how React + JS bundling works. What I don't know is how you've laid out your own project.)

You'll want to make sure dir corresponds to where index.html exists (the entry point for your app) and also points to where your JS bundles are.

@elithrar elithrar self-assigned this Apr 5, 2019
@Dominik-Robert
Copy link
Author

So this is my root folder:

CHANGELOG.md
config.json
cronjob.go
frontend
main.go
metriks.go
pkg
pod.go
src
testsuites.go
.vscode
worker.go

and frontend looks like:

build
.git
.gitignore
node_modules
package.json
package-lock.json
public
README.md
src

@Dominik-Robert
Copy link
Author

Dominik-Robert commented Apr 5, 2019

Actually with this file system structure and the go code /react code from above I get a blank page on localhost:8000 and a 404 on /app or /signIn

EDIT:

It work now. The problem is I have to create for every route in the react framework a route in go. So it looks like:

        router.PathPrefix("/signIn").Handler(http.StripPrefix("/signIn", http.FileServer(http.Dir("frontend/build"))))
	router.PathPrefix("/app/").Handler(http.StripPrefix("/app/", http.FileServer(http.Dir("frontend/build"))))
	router.PathPrefix("/").Handler(http.StripPrefix("/", http.FileServer(http.Dir("frontend/build"))))

Is it possible to this in a function to check the authorization, before serving a file?

Thank you very much for your help!

@elithrar
Copy link
Contributor

elithrar commented Apr 5, 2019 via email

@Dominik-Robert
Copy link
Author

The index.html is in frontend/build
The bundled JS are in frontend/build/static/js/

Can I put this in a function? I want to call an authentication method and only if this is true, serve the files

@Dominik-Robert
Copy link
Author

I have a question to this. I want to make subrouter with static files. I think this should be possible.

Have you a code snippet from that?

I tried it with :
router := mux.NewRouter().StrictSlash(false)

app := router.PathPrefix("/app").Subrouter()
api := router.PathPrefix("/api/v1").Subrouter()

auth := router.PathPrefix("/login").Subrouter()

//app.Use(authenticationMiddleware)
auth.Path("/").Handler(http.FileServer(http.Dir("frontend/build")))

//app.PathPrefix("/app/").Handler(http.StripPrefix("/app/", http.FileServer(http.Dir("frontend/build"))))
app.PathPrefix("/app/").Handler(http.StripPrefix("/app/", http.FileServer(http.Dir("frontend/build"))))

but I wans't successfull with that. I can run it, but don't find the routes who match.

Normally I think this should be:

localhost:8080/app/app/
localhost:8080/login

Thank you very much.

@lacek
Copy link

lacek commented May 10, 2019

I have a question to this. I want to make subrouter with static files. I think this should be possible.

Have you a code snippet from that?

I tried it with :
router := mux.NewRouter().StrictSlash(false)

app := router.PathPrefix("/app").Subrouter()
api := router.PathPrefix("/api/v1").Subrouter()

auth := router.PathPrefix("/login").Subrouter()

//app.Use(authenticationMiddleware)
auth.Path("/").Handler(http.FileServer(http.Dir("frontend/build")))

//app.PathPrefix("/app/").Handler(http.StripPrefix("/app/", http.FileServer(http.Dir("frontend/build"))))
app.PathPrefix("/app/").Handler(http.StripPrefix("/app/", http.FileServer(http.Dir("frontend/build"))))

but I wans't successfull with that. I can run it, but don't find the routes who match.

Normally I think this should be:

localhost:8080/app/app/
localhost:8080/login

Thank you very much.

Since you are using Create React App, I assume you are building a Single Page Application (SPA) that handles the UI and the go server serves APIs for it.

From your code snippet, we can see that the /login route is served from the frontend. But you are trying to add an authentication middleware to it. I wonder if you have confused backend routing with frontend routing. An authentication middleware at the go server is usually supposed to restrict access of certain APIs (because you have an SPA). You should probably have your frontend to check the authentication status and redirect to the frontend login page when necessary.

I guess what you need at your go server should mainly be 2 routes:

  1. /api/v1: to serve the APIs for the frontend application
  2. /: to serve anything else from the frontend/build

A simplest solution would be changing your React app to use HashRouter of react-router instead of BrowserRouter shown in the basic example. Then you won't need to duplicate every frontend route in your go server:

router := mux.NewRouter()

api := router.PathPrefix("/api/v1").Subrouter()
// register routes and middleware for APIs

// serve everything else from the build directory
router.PathPrefix("/").Handler(http.FileServer(http.Dir(dir)))

If you really need to use BrowserRouter, I don't see a trivial way to use http.FileServer and mux without customisation. When using react-router's BrowserRouter, your server may receive URL request that contains frontend paths (e.g. /signIn which you are currently having). The server thus need to handle 3 cases:

  1. if request begins with /api/v1, serve from go handlers
  2. else if request path is a file is found in build folder, serve it directly
  3. else serve index.html from build folder

There's a NotFoundHandler in mux but it only handles unmatched routes. We already have / mapped to the build folder so NotFoundHandler won't help.

Digging into the http.FileServer, we can see that it responds with 404 error immediately if file is not found. There's no way to inject a special handler.

Therefore, you will need to write your own static file handler (or use an existing library if there is) if you insist to use BrowserRouter.

@fharding1
Copy link
Contributor

Relevant: #475 (comment)

@elithrar
Copy link
Contributor

It might be worth adding a new section to the docs with an example that extends the Static Files example, @fharding1 - thoughts?

@fharding1
Copy link
Contributor

Sure, I can do that.

@elithrar
Copy link
Contributor

elithrar commented Jul 6, 2019

See #493

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging a pull request may close this issue.

4 participants