SEO的にはURLにキーワードを含めるのはいいことだと言うじゃないですか。
ドルフィン・シーエムエス「URLに含めるキーワードの最適なSEOとは」
Tumblrのブログとかでも記事のタイトルをURLに入れてくれるし、これを絶賛運営中の「THE TOURNAMENT」でもやりたい。
もちろん日本語でもオッケーな感じで。
というわけでいろいろやってみた結果のメモです。
近年まれに見る長期戦になってつかれた。。
基本方針
というわけで調べてみたけど、どうやらやり方もいくつかあったりするみたいなので
自分の状況とあわせてどんな感じにするか考えてみた。
どんなURLにするか
今回やりたいのは、tournamentモデルのshow画面で、リソースの名前をURLに入れること。
やり方として考えられるのは、
#リソース名がhogehogeの場合
(0) http://the-tournament.jp/tournaments/58 #=>デフォルト(ID)
(1) http://the-tournament.jp/tournaments/58-hogehoge #=>リソース名とIDを連結して使用
(2) http://the-tournament.jp/tournaments/hogehoge #=>IDをリソース名で置き換え
(3) http://the-tournament.jp/tournaments/58/hogehoge #=>IDの下の階層でリソース名を表示
とりあえずこんなとこですかね。
①リソース名とID連結パターン
これは最初選択肢になかったけど、調べてると一番お手軽に実装できる方法としてけっこう人気みたい。
inBoundio「SEO for Ruby on Rails (Complete Guide)」
なんとmodelのメソッドをちょっとoverrideするだけで実装が完了してしまいます。
# models/tournament.rb
class Tournament < ActiveRecord::Base
def to_param
"#{id}-#{title}".parameterize
end
end
これだけ。すでに設置してるlink_toとかのリンクも勝手に置き換わって、かつこの連結したIDでアクセスするとRailsが勝手に読み取って今まで通りのtournament#showを表示してくれます。
簡単で副作用がないのがいいですね。
IDをつけるから名前を一意にする必要がないのも便利です。
②IDをリソース名で置き換えパターン
次はちょっとがんばるときの王道。リソース名を完全にIDの代わりに使っちゃいます。
friendly_idって専用のgemを使うのがいいらしい。
TreeHouseBlog「Creating Vanity URLs in Rails」
リソース名を単体でキーに使ってるので、このパターンやと
名前の重複とか名前が変わった時とかの対応をどうするか問題が発生します。
このgemを使うとそのへんをうまいこと処理してくれるっぽい。
(変更の履歴をとっといてリダイレクトしてくれたりとか)
③IDの下の階層でリソース名を表示パターン
調べててもほとんど情報がなかったけど、元々やりたかったのはこれです。
tumblrとかstackoverflowの記事リンクでもこれになってる。
#stackoverflow:記事IDのあとにタイトル
http://stackoverflow.com/questions/9169101/friendly-id-generate-slug-with-id/
#tumblr:同じく記事IDのあとにタイトル
http://blog.notsobad.jp/post/87809261001/rails
大手も使ってる方法やのになんで全然情報がないんだろう。
調べ方が悪かっただけかな。。
しかし日本語の扱いがネックでして
というわけで3つくらい方法が見つかったけど、日本語の扱いが大きなネックになって
結局当初の想定どおり「③IDの下の階層でリソース名を表示」パターンでやることにしました。
「①リソース名とID連結」パターンは初めて知って簡単でよかったんやけど、日本語がうまく扱えずしょんぼり。(だれか解決法知ってたら教えてください)
「②IDをリソース名で置き換え」パターンも同じく日本語が空文字になったり大変そうで、かつgemを使いこなして一意制御とかやり切るのも面倒そうなのでパス。
「③IDの下の階層でリソース名を表示」パターンは、結局IDで一意判別しててるのでリソース名はあくまでおまけのオプション。
なので日本語の扱いとかも雑でいい気がしてて、いいんじゃないかなと。
あとnestするのにあんまり長いURLもいやでして
あと選択基準はもう1個あって、nested_resourceしたときにURLが長くなって気持ち悪い問題です。
tournamentリソースはplayersとgamesというリソースをnestしてて、
http://the-tournament.jp/tournaments/58/players/
http://the-tournament.jp/tournaments/58/games/
というパスが用意されてます。
ここで断念した2つの方法やと、このID部分が長いのに置き換わるので
URLがちょっと長すぎて気持ち悪いかなーとか(主観)。
長すぎるというか、nestしてるいろんなリソースにアクセスするときに
いちいちリソース名つけてまわらんでも…って感覚が近いですかね。
今回採用した方式やと逆にnestと階層かぶっちゃうからそっちまで変えるの大変やし、あくまでtournament#showのときだけprettyなURL、nestしたリソースとかにアクセスする際は
もともとのIDだけ使う方式でいってみようかと。
最終的にこんな感じにしてみようと思います
というわけで、最終的にこんな感じのroutingにするのがゴールです(長かった…)。
リソース名が「ほげほげ」だとすると、
#「正」となるURLとリダイレクト設定
http://the-tournament.jp/tournaments/58/ほげほげ #=> これが「正」。tournament#show
http://the-tournament.jp/tournaments/58 #=> 上記URLにリダイレクト
http://the-tournament.jp/tournaments/58/ちがう名前 #=> 上記URLにリダイレクト
# nestされたリソースとか
http://the-tournament.jp/tournaments/58/players #=> players#index
http://the-tournament.jp/tournaments/58/games #=> games#index
http://the-tournament.jp/tournaments/58/new #=> tournament#new
http://the-tournament.jp/tournaments/58/edit #=> tournamnet#edit
ふう。
最後のほうに載せてるように、nestリソースとかデフォルトのactionとかとの
判別をどうするか問題が出てきますね。
実装
それではいよいよ実装していきますか。
しかし方針さえ決まってれば実装はそんなに難しくなかったりします。
routingをいじる
まずはroutingの設定。これが今回の肝ですな。
akkunchoi.github.com「Rails3 routes.rb まとめ」
久しぶりにroutingいじったので設定の方法をいろいろ参考にさせてもらいました。
# config/routes.rb
resources :tournaments do
resources :players
resources :games
end
match 'tournaments/:id/(:title)', to: 'tournaments#show', via: :get, as: :pretty_tournament
これだけ。上のほうはもともと設定してるやつなので、追加は実質最後の1行だけです。
まず
'tournaments/:id/(:title)'
の部分は、idのあとに何か入ってたらparams[:title]で受け取るって感じ。
かつ()で囲ってるので、別に指定なくてもいいんですけどねという謙虚な気持ちを表現しています。(もちろん実際そうなる仕様です)
で to: で今までどおり#showのページを表示するようにさせて、
as: でこのパスに名前をつけときました。ここは趣味です。なくても大丈夫。
設定はこれだけなんやけど注意するのはこの1行を記述する場所で、
必ずresources: tournamentsより後に書くようにします。
routingは上から優先して適用されるので、
http://the-tournament.jp/tournaments/58/players
とかのURLでアクセスが来た際、上のresources内でこのアドレスがキャッチされて
今までどおりのページに処理が渡ります。
逆にしちゃうと、playersとかにアクセスしてもそれがリソース名として処理されちゃうので
tournamnets#showページに行ってしまいます。。
nestリソースだけじゃなくてnew, editとかのリンクもこれで問題なく処理されます。
この時点で手動でURL叩いてみて、ちゃんと
名前付きのURLが機能してるのを確認しておきましょう。
予約語を設定しとく
しかしこのままだとtournamentの名前に
「players」とかつけたらどうなるんだ問題が出てきます。
まぁどうなるかというと「players」大会のtournament#showに行こうとしても
players#index(プレイヤー一覧画面)に行ってしまって困る、という感じですね。
これを防ぐにはそもそもそんな名前で大会を登録させないようにするしかないんじゃないかと。
登録されるとつらい名前問題です。
Qiita「登録されるとつらいユーザー名リスト」
一旦ここまで網羅的にやらんでも大丈夫かなということで、最低限の制御だけ追加してみました。
validates :title, exclusion: {in: %w(index new edit players games)}
これでこんな名前で登録しようとすると、「予約されてます」ってエラーメッセージが出るように。
いいですねいいですね。
viewでリンクを修正する
routingが設定できたら次は実際にリンクを表示してるところのパスを修正していきます。
まぁこのあと旧URLからのリダイレクトを設定するんですが、最初からちゃんと飛ばすほうがいいですよね。
さっきroutingでas: pretty_tournamentって名づけておいたので、
# 旧URL
- link_to tournament_path(@tournament)
# 新URL
- link_to pretty_tournament_path(@tournament, @tournaemnt.title)
こんな感じでちゃんとリンクが作成されるはず。渡す引数が1個増えてるので注意です。
prettyなURLにリダイレクト
長かったけどそろそろ仕上げ。最後に旧URLにアクセスが合った場合、もしくは
リソース名に変な値が入ってた場合に正しいprettyなURLにリダイレクトするようにします。
config/routes.rbで定義できたらいいんやけど、残念ながらその時点では
なんていう名前をURLに含めるかが判断できないので、controllerに処理を追加します。
# controllers/tournamens_controller.rb
def show
redirect_to pretty_tournament_path(@tournament, @tournament.title), status: 301 unless params[:title]==@tournament.title
...
end
URLでidのあとにつけてる名前がparams[:title]として取得できるので、
それを正しいリソース名と比較して違うときだけリダイレクト発動。
statusオプションをつけてちゃんと301でリダイレクト。
エンコードとかの関係で日本語の比較がうまくいかんかったら無限ループしそうなので、
エラーが続くようならparams[:title]が空かどうかだけの判定にしてもいいかもしれない。。
というわけでいろいろがんばってみたけど、日本語の扱いとかリダイレクトの設定とかほんとにこんな感じでいいのか色々不安です。
おかしいところとかあればご指摘ください。