`app` subdomain routing!

September 19, 2023

Hi there y'all.

Today I am here to write to you about the usage of `app` (or `admin`, or `api`) as a subdomain and how to use constraints subdomain: 'app' in routes.rb.

It is a very common strategy to host your website (the marketing one) in a separate app, perhaps using a CMS I shall not name, that is separate from your RubyOnRails app.

This post is brought to you by the fact that I am reversing that scenario in one of my small apps, for a couple of reasons, being cost one of them but also because I prefer to manage 1 app in 1 cloud provider instead of 2 apps in 2 cloud providers.

So, in the process of doing that I ran into some issues and got a chance to use Rails routes constraints again to ensure routes are only available where they are needed.

My goal is also simple: merge the two together in a single Rails app, hosted in Fly.io.

SIDE NOTE: if you have some feedback to share about Fly, I'd love to hear. I've been using Fly for about 4 months and so far I have a love and hate relationship with them. A few days ago, I had decided to leave, the thoughts of going back to Heroku ran through my mind, but a lunch in between made me realize the problem was indeed my local wifi instability! :-D

So, back to Rails Routes Constraints!

In the process of merging the marketing site with the rails app, I've decided to change the Rails routes to become like this:

constraints subdomain: 'app' do
  devise_for :users
  # passkeys routes
  # other routes...
  ...
end

The goal was to ensure all the existing routes for the Rails app was only available when browsing through the app, under `app` subdomain.

When you set that up, you need to have the subdomain `app` available for your localhost, and you may need `app.staging` for your Staging.

To have it available for your local development environment, you just need to add the following to /etc/hosts:

127.0.0.1	spkeym.localhost
127.0.0.1	app.spkeym.localhost

And this in `environments/development.rb`:

config.hosts += ['spkeym.localhost', 'app.spkeym.localhost']

So, now Devise sign in route `users/sign_in` are only available in http://app.spkeym.localhost:3000 and so it all other routes you have in your Rails app.

Passkeys routes... Oh... passkeys routes are totally ok, they are under the Rails app and under app subdomain... But why are the tests failing?! So... the browsers (Safari & Chrome)... So maybe that should be the goal of another post! :-D

Before I wrap this up without further side notes, there is one more change you need for Staging environment: you must take `staging` out of the subdomain, so it doesn't conflict with routes.rb.

You do that with by adding the following in to `environments/staging.rb`:

config.action_dispatch.tld_length = 2

The reason you need that is because Rails, inside actionpack, would consider anything before `my_domain.com` as your subdomain.

If you keep the default value for tld_length, the subdomain would be `app.staging` and you'd get a routing error, given routes.rb has `constraints subdomain: app`.

Once the routes in app subdomain and staging are taken care, you need to ensure your site routes aren't available for your app subdomain. To accomplish you can create another constraint block with `subdomain: false`:

constraints subdomain: false do
  # Website pages
  get 'contact', to: 'home#contact'
  get 'about', to: 'home#about'
  ...
end

So now, your Rails app routes are only available under the app subdomain, while your website routes, now part of your Rails app, are only available for your root domain! Voila?!

Not quite voila... There is still one issue left (that I have not found a good solution): the root_path!

I still had root_path defined outside the constraints blocks as:

root 'home#index'

In this situation, the root path is also avaible for the app subdomain. And if you move it inside `subdomain: false` (where the website routes are defined), your app will render the default index.html shipped with Rails (for the app subdomain).

I believe the routing needs a better way to deal with this scenario. Or I have not found the good solution yet!

I did try a few things (some are listed below), but I was still not happy with the outcome.

root 'home#index', constraints: { subdomain: false }

constraints subdomain: 'app' do
  get '/', to: 'home#index', constraints: { subdomain: false }
  ...
end
            

So, esteemed gentleperson 🌈... This is it for this post...

But... but... but... but before I go, let me give you a glimpse of the next blog post: if you are in the situation above, and you want to override Devise `after_sign_out_path_for` to redirect from the subdomain to the root domain you will have to do some back flips!

I also haven't found a good enough solution for it (the issue `after_sign_out_path_for`). And I've dug into Devise's code (and Rails code for the root_path)...

    So I end this post with 2 topics that I would like spend some of my time:
    1. Rails routing for root_path when using constraints subdomain
    2. Devise after_sign_out_path_for to redirect from app subdomain to root domain

If you are still here with me and have a preference between one or the other... please tell me! :-D

If you liked it, say Hi on !

If you have any comments, write me a message on (or by the good and old fashioned ! :-D)