Route all non-static file requests to index.html (in development)

I’m using Middleman to develop an AngularJS app. I want to route all requests that aren’t for static files to index.html and let AngularJS handle the routing.

In production I would use .htaccess, how can I get this same behavior in development (I run the server via bundle exec middleman)?

Hi @abhik,

I’m not exactly sure what you’re asking for. Middleman is designed to build a static site. Routing requests dynamically is something that you would handle at the server-level.

It would be odd for Middleman to provide this feature, as users might mistakenly think that setting up this kind of routing in development would somehow magically make it work in production. Middleman doesn’t make any assumptions about what kind of server you’re using.

I don’t know anything about your project, and I also don’t know much about AngularJS, but I would question why you would need to redirect every “non-static” page (I’m not sure what that means in this context). Angular handles the routing for you, doesn’t it? This is a very strange request.

With those caveats, there is in fact a way to get this behaviour in development. You can write custom Rack Middleware to run alongside the development server.

Here’s how I would implement this. Inside a new file, called missing_redirector.rb:

class MissingRedirector
  def initialize(app)
    @app = app
  end

  def call(env)
    status, headers, response = @app.call(env)

    # Redirect any missing pages to the root route
    if status == 404
      status = 302
      headers['Location'] = '/'
    end

    [status, headers, response]
  end
end

Require the Middleware and ask Middleman to use it in your config.rb:

require './missing_redirector'

use MissingRedirector

That should do the trick.

Hope that helps!

Thanks for the reply. AngularJS does handle routing for you, but the site is served from index.html. If I send a person a link to www.example.com/register the server would try to find the resource for register (rather than serve index.html). In production with Nginx, you would change .htaccess to always serve index.html for any route that isn’t a static file. In development, I’d like to do the same. I’ll try this out. Thanks!

No problem! Best of luck.

For what it’s worth, I wouldn’t recommend redirecting all missing files to one route. You may have unintended consequences if you do. Consider what happens if a browser requests a missing /favicon.ico, a crawler demands a missing /robots.txt or a bot script tries known standard URLs for vulnerabilities. Are you sure you want the default behaviour to be a redirect to index.html in these cases? What about the cases you don’t know about?

Better to be explicit about the routes you want to handle (e.g. /register). I’m not aware of nginx supporting .htaccess files (thankfully), but you could use nginx’s include to read a map from your build, and support this in both development and production. I’m doing this at the moment.

The problem is legitimate. And redirecting to the root node is not quite correct. For Angular or whatever frontend routing framework to get to the URL, you need to serve the content of the root at the requested URL. This is what the following does. Thanks to Aupajo for the help.

class ServeRoot
  def initialize(app)
    @app = app
  end

  def call(env)
    status, headers, response = @app.call(env)

    # Redirect any missing pages to the root route
    if status == 404
      status = 200
      contents = File.read('./build/index.html')
      headers['Content-Length'] = contents.length.to_s
      response.body = contents.split('\n')
    end

    [status, headers, response]
  end
end

In case anyone is still having this issue (like I was), the solution @matstc works to serve the compiled index.html, but if you’re running in dev using bundle exec middleman then you may not always have the compiled index.html file to serve (plus any changes to index.html will require a bundle exec middleman build step).

I slightly modified what @matstc suggested, but to serve the index without having to build first:

```ruby
class ServeRoot
  def initialize(app)
    @app = app
  end

  def call(env)
    status, headers, response = @app.call(env)

    # Redirect any missing pages to the root route
    if status == 404
      env['PATH_INFO'] = '/' # All dynamic routes should serve index.html
      status, headers, response = @app.call(env)
    end

    [status, headers, response]
  end
end
```