How to enforce trailing slash in Firebase hosting for directories only

103 views Asked by At

I am using Firebase Hosting with cleanUrls.

Firebase Hosting also allows to enforce trailing slashes in all URLs with:

"hosting": {
  "trailingSlash": true
}

This will force a 301 redirect if the user requests a page without using a trailing slash. However, I'd like to enforce this redirect only for directories (i.e.: for serving index.html files).

According the documentation, you can leave trailingSlash as undefined. This will:

  • Serve /foo/bar/page with a 200 (/foo/bar/page.html)
  • Serve /foo/bar/ with a 200 (/foo/bar/index.html)
  • Serve /foo/bar with a 200 (/foo/bar/index.html)

The last part is unexpected, since I'd like to enforce trailing slashes for directories.

How can I enforce a 301 redirection for all directories to enforce a trailing slash only for them? In example:

  • Serve /foo/bar/page with a 200 (/foo/bar/page.html)
  • Serve /foo/bar/ with a 200 (/foo/bar/index.html)
  • Redirect /foo/bar with a 301 to /foo/bar/

Note there can be many directories so a solution that avoids writing a separate rule for each one is much preferred for maintainability.

Having /foo/bar return a 404 would be okay too, but 301 is preferred.

1

There are 1 answers

7
VonC On

Since Firebase Hosting's configuration does not automatically differentiate between files and directories in the way traditional servers might, you could try and manually specify redirect rules for your known directory paths.

But:

  • each directory would need a redirect rule. For projects with many directories, this can become cumbersome.
  • as commented, this does not work: the redirect rule for adding a trailing slash is too general or does not correctly discriminate between files and directories, causing Firebase Hosting to repeatedly attempt to redirect to the same path, adding a slash each time, ... which results in a loop.

Note there can be many directories so a solution that avoids writing a separate rule for each one is much preferred for maintainability.

Then (and this is not tested) you would need to use Cloud Functions for Firebase or Firebase Hosting's integration with Cloud Run to programmatically handle requests. That allows you to implement logic that checks if a requested path corresponds to a directory and enforce a trailing slash through redirection.

Using a Cloud Function, that function intercepts HTTP requests, checks if the request URL corresponds to a directory (by checking if it maps to an index.html file in your public directory), and redirects to the same URL with a trailing slash if so.
A pseudo-code example would be:

const functions = require('firebase-functions');
const path = require('path');
const os = require('os');
const fs = require('fs-extra');

exports.addTrailingSlash = functions.https.onRequest(async (req, res) => {
  // Extract the path from the request URL
  const urlPath = req.path;

  // Construct the file system path to where the file would be located
  const filePath = path.join(os.tmpdir(), 'public', urlPath);

  // Check if an index.html exists for this path
  if (await fs.pathExists(path.join(filePath, 'index.html'))) {
    // Directory exists, redirect to path with trailing slash
    res.redirect(301, `${urlPath}/`);
  } else {
    // Not a directory or no index.html, handle normally
    // That might involve serving the file directly, showing a 404, etc.
  }
});

Route requests through this Cloud Function will use the rewrites feature in your firebase.json, allowing the function to process and redirect directory requests accordingly.
Your firebase.json would need a rewrite rule to direct traffic to this function for handling:

{
  "hosting": {
    "rewrites": [
      {
        "source": "**",
        "function": "addTrailingSlash"
      }
    ],
    // Other configurations
  }
}

But: routing all requests through a Cloud Function could introduce latency: do check the potential performance impact.
And implementing logic with Cloud Functions or Cloud Run adds complexity to your project and may incur costs based on your usage, so you might have to do some Cloud FinOps.