Reach Router breaks with Gatsby path prefixes

1.6k Views Asked by At

I have a Gatsby site served at http://server/test (i.e., not at the server's root) that works while in gatsby develop. But gatsby build and then serving up the site, it fails.

Gatsby's instructions say to modify gatsby-config.js to include pathPrefix: '/test' and then when building the site, run gatsby build --prefix-paths, which I do.

This works completely for the static routes contained in my src/pages.

The issue appears to be that Reach Router dynamic/private/protected routes do not get modified to take into account the path prefix, but the static routes do. So in this file structure:

~/Documents/app/src/pages
- index.tsx (landing page)
- app.tsx (app root page that includes the router config)

These two pages serve up correctly (after configuring Apache/nginx to serve var/www/html at http://localhost/test) as http://localhost/test and http://localhost/test/app.

Here is the router:

<Layout>
      <ErrorAlert/>
      <Router basepath={"/app"}>
        <Login default path="/login"/>
        <PrivateRoute path="/projects" component={Projects}/>
        <PrivateRoute path="/project" component={ProjectDetails} />
        <PrivateRoute path="/project/:projectId" component={ProjectDetails} />
        <PrivateRoute path="/profile" component={Profile} />
        <PrivateRoute path="/profile/edit" component={EditProfile} />
        <PrivateRoute path="/profile/change-password" component={EditPassword} />
        <PrivateRoute path="/settings" component={Settings}/>
      </Router>
    </Layout>

Here is PrivateRoute:

export const PrivateRoute:React.FC<PrivateRouteProps> = ({component, location, ...rest}) => {
  const token = useSelector(tokenLens.get) // Option<string>
  const dispatcher = useDispatch()

  const storeAttemptedPageUrl = flow(storeAttemptedVisit, dispatcher)
  const isUnauthorizedVisit = () => isNone(token) && location.pathname !== '/app/login'

  const sendToLogin = () => {
    storeAttemptedPageUrl(location.pathname)
    navigate('/app/login')
    return <div/>
  }

  React.useEffect(() => {
    if(isUnauthorizedVisit()) {
      sendToLogin()
    } else {
      pipe(
        getAndStoreProfile()
      )()
    }
  }, [token])
  return pipe(token, map(()=>component(rest)), getOrElse(()=><></>)) //IE, render component if auth token exists, otherwise temporarily render blank page until redirect to login is complete
}

http://localhost/test takes me to the contents of pages/index.tsx, which contains a <Link> to "app". (which gets "/test" prepended). So far so good. The default route is Login, which gets shown. I fill in my login info and hit submit, which (after authenticating) triggers in my login component the following code: navigate('/app/projects').

This ought to load the component provided by <PrivateRoute path="/projects" component={Projects}/>. Instead, nothing happens. The page just sits at login. It appears the app is trying to load the component using the wrong route (confounded by the path prefix stuff), not finding it, and redirecting to the default (the login). We're already on the default, so it appears nothing happens.

Devtools confirms that the actual authentication is occurring.

Similar issues on SO?

No. Here is one with a person having a subtly different issue about static routes that came down to misconfiguration of the actual server.

Here is a discussion on Gatsby about what looks like the same issue I'm having. But recognizing that this is kind of a support question, not a bug, and that issue has been closed for staleness, I'm asking here. I tried both of the suggested solutions on that page to no avail, and Gatsby has no documentation about path prefixes and reach router.

One suggested fix is to use __PATH_PREFIX__ as your reach router's basepath, and claims Gatsby makes it a global variable. This does not appear to be true. When in the site, I open dev console and type __PATH_PREFIX__, and it's undefined.

The other fix does not work either.

This cannot possibly be an existing bug because there's no way 100% of the people using Gatsby are serving on document root with only static pages and no dynamic ones protected behind authentication.

1

There are 1 best solutions below

1
On

The answer turns out to be pretty simple, although tough to track down. In the file containing the router:

import { withPrefix } from 'gatsby'

<Router basepath={withPrefix("/app")}>

That's the only change needed.