Make server respond with 404 status on unmatched URLs

Like the title says, I want the server to respond with a 404 status code to requests for unidentified URLs. There must somehow be a way to do this using the WebApp API, but the documentation is very sparse and I cannot figure out how.

Most client side routers already include a 404 handler, and using them will make your life much simpler.
For example, ostrio:flow-router-extra does this like so:

// Create 404 route (catch-all)
FlowRouter.route('*', {
  action() {
    // Show 404 error page using Blaze
    this.render('notFound');

    // Can be used with BlazeLayout,
    // and ReactLayout for React-based apps
  }
});

If you really want to do this manually on the server side, there’s a lot of checking you need to do:

import { WebApp } from 'meteor/webapp';

import fs from 'fs';
import path from 'path';
import url from 'url';

const publicDirPath = path.join(
    __meteor_bootstrap__.serverDir,
    '../web.browser/app'
);
WebApp.connectHandlers.use(function(req, res, next) {
    const urlParts = url.parse(req.url);
    if (urlParts.pathname === '/') {
        // We just want to serve the app bundle
        // Use meteor default behaviour
        return next();
    }

    if (fs.existsSync(path.join(publicDirPath, urlParts.pathname))) {
        // File exists in the public dir
        // Use meteor default behaviour
        return next();
    }

   
    // Check for other routes in connect's middleware stack
    if (
        WebApp.connectHandlers.stack.some(({ route }) => route === urlParts.pathname) ||
        WebApp.rawConnectHandlers.stack.some(({ route }) => route === urlParts.pathname)
        ) {
        // If found, we just yield control to the next middleware.
        return next();
    }

    // Else we 404, if you want to serve a 404 page, add that here as well.
    res.statusCode = 404;
    res.statusMessage = 'Not found';
    res.end();
});

If you use a router, you will need to expose the routes to the server and check those routes as well.

Be really careful for things like meteor’s built in dynamic imports fetch route that you don’t overwrite it as well

3 Likes

Thank you very much for that code and the help!

Using react-router on the client, I can easily render a 404 template for unmatched routes. The problem is that the HTTP header remains the same with a 200 status code. Are you perhaps saying that there is an easier way around this?

If you’re doing server side rendering (SSR), it looks like React-router can handle this quite well: https://reacttraining.com/react-router/web/guides/server-rendering/

Integrated with Meteor’s server-render package would look something like this:

import React from 'react';

import { renderToString } from 'react-dom/server';
import { onPageLoad } from 'meteor/server-render';
import { StaticRouter } from 'react-router';

import App from './App';

onPageLoad(sink => {
    const context = {};

    sink.renderIntoElementById(
        'app', // Assuming you're rendering into an element where id="app"
        renderToString(
            <StaticRouter location={sink.request.url} context={context}>
                <App />
            </StaticRouter>
        )
    );
    if (context.status) sink.setStatusCode(context.status);
    if (context.url) sink.redirect(context.url);
});

And App would use something like the Status and NotFound components on the page linked above


Unfortunately the docs for server-render don’t explain that you can use sink.setStatusCode, but the github page has it: https://github.com/meteor/meteor/tree/master/packages/server-render

1 Like

Thanks again for that. Still considering whether to use server-render or prerender.io, so that may well be useful!

if you have react and react-router, use server-render

also with react and flow-router its feasible to use server-render

use prerender as last resort

Why not both? I would prefer to use client-side rendering for the main functionality of my app, but SSR is probably better for certain features/pages, and I would need something like prerender.io to make the content of the client-side routes accessible to search engine web-crawlers.