6

Trying to implement SSR with Firebase so I'm using a function to prerender each page of a React App. It's working well except the home page, so it must be either the match is wrong on the firebase redirect or possibly on the express route itself.

firebase.json

{
  "firestore": {
    "rules": "firestore.rules",
    "indexes": "firestore.indexes.json"
  },
  "functions": {
    "predeploy": [
      "npm --prefix \"$RESOURCE_DIR\" run lint"
    ]
  },
  "hosting": {
    "public": "build",
    "rewrites": [
      {
        "source": "**",
        "function": "contentServer"
      }
    ],
    "ignore": [
      "firebase.json",
      "**/.*",
      "**/node_modules/**"
    ]
  }
}

contentServer.js

import * as functions from 'firebase-functions';
import * as fs from 'fs';
import * as path from 'path';

import React from 'react';
import Helmet from 'react-helmet';
import { renderToString } from 'react-dom/server';
import Server from '../browser/Server.js';

const express = require('express');

const app = express();

// might be this? Also tried /**

app.get(['**'], (request, response) => {
  const context = {};
  const location = request.url;
  console.log('Processing request for ', location);

  let appCode;
  try {
    appCode = renderToString(<Server context={context} location={location} />);
  } catch (err) {
    appCode = 'with error';
  }

  // const appState = {
  //   pageTitle: 'Hello World',
  // };

  // const preloadedState = JSON.stringify(appState).replace(/</g, '\\u003c');
  const fileName = path.join(__dirname, '../index.html');
  const htmlTemplate = fs.readFileSync(fileName, 'utf8');
  const head = Helmet.renderStatic();

  const responseString = htmlTemplate
    .replace('<div id="root"></div>', `<div id="root">${appCode}</div>`)
    .replace('<title>React App</title>', `${head.title}\n${head.link}`);
  return response.send(responseString);
});

export default functions.https.onRequest(app);

Curl

I run firebase serve --only functions,hosting

Then use curl to check the response:

curl http://localhost:5000 - does not render the home page - just the standard react page
curl http://localhost:5000/ - also does not work - just the standard react page.
curl http://localhost:5000/contact-us - works well and returns the contact us page, all other pages on the site work and trigger the function.
Frank van Puffelen
  • 565,676
  • 79
  • 828
  • 807
Richard G
  • 5,243
  • 11
  • 53
  • 95

1 Answers1

9

If you want redirect every single URL to your host to an express app in Cloud Functions, you will need to do the following:

Make sure there is no index.html in your public hosting folder (otherwise it will always be served with the path /).

Configure Firebase hosting in firebase.json to rewrite all urls to a function (you are currently doing this in your "hosting" block, which is good):

"rewrites": [
  {
    "source": "**",
    "function": "contentServer"
  }
]

Write a Cloud Function exported with the same name as the function in the rewrite, and attach an express app that handles the route wildcarded with *. In index.js in your functions folder, minimally:

const functions = require('firebase-functions')
const express = require('express')

const app = express()

app.get("*", (request, response) => {
    response.send("OK")
})

exports.contentServer = functions.https.onRequest(app)

If you run this locally with firebase serve --only hosting,functions, every path that you send to localhost:5000 will say "OK".

Doug Stevenson
  • 297,357
  • 32
  • 422
  • 441
  • Thx Doug. removing index.html works, it's a pain though, because it's hard to rename that file with the non-ejected create-react-app (from what I've read on the matter). SSR would be working well without needing to eject the webpack config if could change that hosting behaviour? Can I add this as a feature request on Firebase Hosting somehow? – Richard G Apr 28 '18 at 10:45
  • for the record I added index.html to the ignore list. So it works on the deployed environment, but not on the local "firebase serve". Not a big deal. – Richard G Apr 28 '18 at 12:23
  • You can always file feature requests here: https://firebase.google.com/support/contact/bugs-features/ – Doug Stevenson Apr 28 '18 at 16:47
  • @RichardG I found that you dont have to necessarily remove the index.html file if you use the express Router middleware like so. `router.use("^/$", serverRenderer); app.use(router);` – Tope May 22 '18 at 15:55