SEO can be the reason for improve URL namings. However, existing routes are still expected to work properly. After all, someone could have saved a URL or even linked it.
Legacy routes
An example might be landing pages for cities.
The URLs to the cities pages:
# config/routes.rb
Rails.application.routes.draw do
resource :city, only: :show
end
look like this based on unique slugs (e.g. for Berlin): travel.com/city/berlin.
Redirect legacy routes
Let’s assume, that after these landing pages have gone live, one decided to rename the routes, like: travel.com/to/berlin. Of course, the old routes still should return proper response.
The Rails routes generator provides the very nice ActionDispatch::Routing::Redirection#redirect to help out. Basically it redirects the old routes to the new routes. It also automatically sends a status code (by default 301 - Moved permanently) to the browser. With 301 most browsers remove the legacy route from their cache and cache the new route instead. The same practice holds true for search engines.
The routing generator with redirects looks like this for the example:
# config/routes.rb
Rails.application.routes.draw do
# new route
get 'to/:slug', to: 'cities#show', as: :city
# legacy route will be redirected to new route
get 'city/:slug', to: redirect('to/%{slug}')
end
Redirects with constraints
As soon as the redirect depends on logic, it is necessary to outsource this logic into Rack application, just to keep the routes.rb clean. That step also makes sense in case of handling multiple similar redirects.
A classic example is redirecting only the cities routes, which are already revealed (those which may be known at all). That applies to all cities, which were created until a certain time.
For all new cities the 404-page is expected to be rendered.
The extended routing generator:
# config/routes.rb
Rails.application.routes.draw do
get 'to/:slug', to: 'cities#show', as: :city
# The CityRedirector redirects
# just the relevant cities
get 'city/:slug', to: redirect('CityRedirector')
end
The CityRedirector loads all city slugs that were created until 2017-07-01 and checks if the slug parameter of the requested city is included. In case it is not, there is no redirect performed:
class CityRedirector
DUE_DATE = Date.new(2017, 7, 1)
def self.call(params, request)
"to/#{params[:slug]}" if legacy_city_slugs.include?(params[:slug])
end
private
def self.legacy_city_slugs
@@legacy_city_slugs ||= City.where('created_at < ?', DUE_DATE)
.pluck('slug')
end
end
After a some time, it is worthwhile to analyze the outdated routes, if they actually still are requested to some extent. If the amount of requests to the legacy routes is low, it makes sense to think about removing the legacy routes and their redirects likewise.