13

(Updated below with a little more info)

We have a react-based single-page web app that lives on Firebase Hosting. We've been using hash-based routing up to this point (e.g. mysite.com/#/path/to/content). We introduced React Router, which allows us to have prettier URLs (e.g. mysite.com/path/to/content), but now the app doesn't load when we navigate directly to a deep route. Details below:

Using React Router required us to use URL Rewriting in Firebase Hosting. The directions are pretty straightforward--it seems like all you need to do is this:

"rewrites": [ 
    {
    "source": "**",
    "destination": "/index.html"
    }
]

...inside the firebase.json file. In fact, our complete firebase.json file is as follows:

{
"firebase": "",
"public": "dist",
"rules": "rules.json",
"ignore": [
    "firebase.json",
    "**/.*",
    "**/*.map",
    "**/node_modules/**"
],
"rewrites": [ 
    {
    "source": "**",
    "destination": "/index.html"
    }
]
}

Nothing too complicated there (we're doing firebase deploy -f in our build script, if you're wondering why the firebase argument is empty). The linked-to rules.json file is even simpler:

{
   "rules":{
   ".write":"true",
   ".read":"true"
 }
}

When I load the app by going to the base URL and start navigating around, everything is fine. If I go directly to a route at one level deep (e.g. mysite.com/path), that works too.

HOWEVER

If I go directly to a route at a deep level, or even to a top-level route with a trailing slash (e.g. mysite.com/path/ or mysite.com/path/to/content), then the app doesn't load.

Specifically, what happens is the index.html page loads, and then browser goes to load our webpack-created bundle.js file that is referenced in index.html, but what Firebase Hosting returns for that JS file is the content of the index.html file. So then, predictably, what we see in the browser console is this error:

Uncaught SyntaxError: Unexpected token <

...on line 1 of the "bundle file". If I view the content of the bundle file in Chrome's dev tools, line 1 of the bundle file is literally this:

<!doctype html>

...which is then followed by the rest of the HTML from the index.html file.

It seems like what's going on is the Firebase URL rewrite rule is saying "oh, you're trying to reference a JS file--I see I'm supposed to return the content of index.html for everything, so here you go". But that can't be the case all the time, or the site would never work under any circumstances. So why does it work if I start at the site root, but breaks if I paste in a deep URL to the app? I have zero idea.

If it helps, here's how my index.html file references the bundle file:

<script src="bundle.ce843ef7a2ae68e9e319.js"></script></body>

So it seems like a problem with Firebase hosting, but I could also see that maybe I don't understand React Router at some level, and so I've somehow screwed things up with the client-side code in such a way that it's forcing the server to return the wrong thing.

Here's me hoping it's some stupid configuration thing we're missing. Any help is appreciated!

Thanks!

UPDATE: I stripped down our app so that it only returns "hello, world", which bypasses all of the React-based stuff, including React Router, and the problem persists, so I no longer think this has anything to do with React-Router, though I suppose there's still a small chance this could have something to do with React.

hairbo
  • 3,113
  • 2
  • 27
  • 34

4 Answers4

14

I heard back from Firebase Hosting. Apparently, the referenced bundle.js file in our index file needed a slash in front of it, to make it an absolute path. So this:

<script src="/bundle.js"></script>

...instead of this:

<script src="bundle.js"></script>

In case anybody else makes this same silly mistake, I hope this is useful.

hairbo
  • 3,113
  • 2
  • 27
  • 34
  • Thank you! I was having the same problem, keep in mind this will also affect the CSS files references, you need the / on those as well. – Roberto Nov 11 '16 at 09:38
  • @hairbo: Thanks B, never thought I'd see an old co-worker on StackOverflow, much less get help from one! This answer helped me on a side-project. – Ezra Chang Dec 10 '16 at 02:10
  • Nice! Glad to hear it, Ezra! Hope all is well. – hairbo Dec 10 '16 at 02:10
  • 1
    Brilliant stuff, such an oversight on my part and a great explanation here. Thanks. Because the OP talks about react, I'd like to mention that webpack production configuration needed a `output: {publicPath: '/'}` for me. – Priya Ranjan Singh Jul 11 '17 at 17:47
  • 1
    This is unbelievable! I'm here for 3 hours looking in every config item and it was a slash. I'm kind of nervous and happy at the same time – Victor Leal Feb 12 '18 at 17:24
  • It has not solved for me. My webpack-dev-server is giving correct behavior. But using root relative path did not solve the same probelm for me. – sn.anurag Jul 25 '20 at 18:03
3

I encountered the same behavior but the problem was unrelated to bundle.js.

My rewrites section of firebase.json looked like this:

    "rewrites": [
      {
        "source": "**",
        "destination": "index.html"
      }
    ]

and I resolved the problem by using the root relative path instead of the relative path for index.html. (Thanks Ashu!)

    "rewrites": [
      {
        "source": "**",
        "destination": "/index.html"
      }
    ]
Leslie Sage
  • 391
  • 2
  • 8
2

[tl;dr]

This answer is only for educational purposes and in-depth insight to the problem statement and its solution. For copy/paste purposes see this answer provided by @hairbo. It is the short version and answers the question simply and nicely

Every link to resources like images, css, js can be referenced to in 3 ways:

1) Absolute Path

<script src="http://app-url.com/bundle.js"></script>

This is absolute path and resource will be loaded from the specified path without pre-processing the path provided.

2) Relative Path

<script src="bundle.js"></script>

This is relative path and it refers to the resource file as if it is located in current working directory. That is when you are on app-url.com/lvl-1 the relative path becomes app-url.com/bundle.js which provides no problems because that is actually the path to resource.

Problem

But upon going another level deep, i.e. app-url.com/lvl-1/lvl-2, current working level becomes app-url.com/lvl-1/ and the relative path is treated as app-url.com/lvl-1/bundle.js but that is not actually the path to resource. Hence the index.html file mis-behaves because it can't load the required files.

3) Root Relative Path

<script src="/bundle.js"></script>

Adding a slash (/) before the relative path makes it root relative path. In this case all non-absolute paths are treated as paths rooted at the current domain. That is, in this case the path to resource /bundle.js is treated as app-url.com/bundle.js even if current working level of url is app-url/lvl-1/lvl-2/lvl-3/...

Solution Do not use ~relative paths~. Either use absolute paths or root relative paths in all files. This way every path will be treated as expected.

Ashu
  • 2,970
  • 1
  • 19
  • 31
1

I had this same problem and I fixed adding rewrite rules to my firebase.json file.

here is how my firebase.json file looks.

    {
  "hosting": {
    "public": "webApp/build",
    "ignore": [
      "firebase.json",
      "**/.*",
      "**/node_modules/**"
    ],
    "cleanUrls": true,
    "rewrites": [
      {
        "source": "**",
        "destination": "/index.html"
      }
    ]
  },
  "functions": {    
    "source": "functions"
  }
}

sorry for my english, i still learning.