Qiita Teams that are logged in
You are not logged in to any team

Log in to Qiita Team
Community
OrganizationAdvent CalendarQiitadon (β)
Service
Qiita JobsQiita ZineQiita Blog
8
Help us understand the problem. What is going on with this article?
@studioTeaTwo

SPA(single page application)のURLでシェアする

More than 3 years have passed since last update.

本稿は「rails5 APIモードでindex.htmlを配信する」の続きです。
SPA(single page application)のURLでシェアするために少し発展させました。

https://hoge.com/article/1をtwitterでシェアし、そのツイートのリンクをクリックして初めてサイトにアクセスする人がトップページではなくいきなり該当コンテンツを見れるようにします。

自由なシェアこそwebの本懐!SSR(server side rendering)を導入しなくてもやれておきたい。

railsとangularでやっています。OGPタグはアプリ共通のためサムネイルの表示などはできません。

アプローチ

サーバでindex.htmlを返しつつ、ブラウザに到達してからLocationを展開します。
https://hoge.com/article/1を例にしていきます。

  • railsのルーティングでSPAのURLを認知する
  • SPAのURLならパス/article/1を保持しつつindex.htmlを返す
  • フロントでindex.htmlを展開しつつ該当パス/article/1へのルーティングを行う

railsのルーティングでSPAのURLを認知する

まず、前回はget '*path', to: redirect('/')とし、API以外のその他URLを全てリダイレクトしていた箇所をcontrollerに渡すようにする。

routes.rb
get '*path', to: 'static_pages#spa_forward'

次に、リクエストオブジェクトからSPAで定義しているURLを識別します。

class StaticPagesController < ApplicationController

  (...)

  def spa_forward
    # SPAのURLをパターンマッチングする
    if request.original_fullpath =~ /^\/(article|user)(|\/.*)$/
      # SPAのURLの場合の処理を書く
    else
      redirect_to root_url
    end
  end

end

SPAのURLならパス/article/1を保持しつつindex.htmlを返す

3つ方針を考えました。

  1. URLをそのままにしてindex.htmlを返す
  2. SPAのURLをクエリパラメーターに退避して、ブラウザでSPAが自力で展開する
  3. index.htmlにカスタムデータ属性でURLを書き込み、ブラウザでSPAが自力で展開する

1.URLをそのままにしてindex.htmlを返す

もっともシンプルで自然な形です。URLを加工しないので、ブラウザのLocationに/article/1がセットされ、SPAのルーターがキャッチしたらルーティングを開始します。

class StaticPagesController < ApplicationController

  (...)

  def spa_forward
    if request.original_fullpath =~ /^\/(article|user)(|\/.*)$/
      render file: 'public/index.html'
    else
      redirect_to root_url # アセットファイルなどのその他はルートに返す
    end  
  end

end

しかし、status200を返しつつもindex.htmlが返却されません。response headerがtext/plainだしEtag貼られてるしで、railsのAPIモードが何かしら原因ぽさあるがどうすればいいか調べてもわからず断念。railsでなければ、というかAPIモードで無ければこれで十分のはず...。

2.SPAのURLをクエリパラメーターに退避して、ブラウザでSPAが自力で展開する

採用した形式です。URIオブジェクトを起こし、クエリーパラメーターにto_paramでエンコーディングしたパスをセットします。その後はルート/にリダイレクトすれば、ブラウザではhttps://hoge.com/?path=%2Farticle%2F1でURLが認知されます。

class StaticPagesController < ApplicationController

  (...)

  def spa_forward
    if request.original_fullpath =~ /^\/(article|user)(|\/.*)$/
      uri = URI('/')
      # クエリパラメーターにSPAのパス`/article/1`をセットする
      uri.query = { path: request.original_fullpath}.to_param
      # ルートにリダイレクトする
      redirect_to uri.to_s # -> /?path=%2Farticle%2F1
    else
      redirect_to root_url # アセットファイルなどのその他はルートに返す
    end  
  end

end

3.index.htmlにカスタムデータ属性でURLを書き込み、ブラウザでSPAが自力で展開する

軽くindex.htmlにtag("div", :data => {:path => @path})あたりをセットするようなノリで考えたが、やはりAPIモードの壁にあたり断念。やるならstringで自力でviewを作りファイルに書き出しtextで返す感じかな。rails APIモードはネイティブアプリやマイクロサービスのためにあるんやね、webページはほんと持てないね。

フロントでindex.htmlを展開しつつ該当パス/article/1へのルーティングを行う

クエリーパラメーターで渡されたパスがLocationにセットされているので、historyAPIでLocaitonから取り出しデコードして再びLocationにセットします。

app.component.ts
import { Component } from '@angular/core';
import { Location } from '@angular/common';
import { UrlSerializer } from '@angular/router';

@Component({
  selector: 'app-root',
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.scss']
})
export class AppComponent {

  constructor(
    private location: Location,
    private urlSerializer: UrlSerializer,
  ) {
    // path文字列が含まれているならLocationを書き換える
    if (this.location.path().match(/path/)) {
      // URLをパースしてクエリーパラメーターをデコードする
      const urlTree = this.urlSerializer.parse(this.location.path());
      // クエリパラーメーターに退避していたSPAのパスをLocationにセットする
      this.location.replaceState(urlTree.queryParams['path']);
    }
  }

}

urlTreeオブジェクトの中身はこんなんになっています。%2Farticle%2F1/article/1に戻っています。

{
  "root":{
    "segments":[],
    "children":{},
    "parent":null
  },
  "queryParams":{
    "path":"/article/1"
  },
  "fragment":null
}  

できたこと

  • ブラウザのアドレスバーにhttps://hoge.com/article/1を手入力して/article/1を開ける
  • 別サイトのaタグにhttps://hoge.com/article/1を埋め込んで/article/1を開ける
  • iPhoneアプリのtwitter/LINE/facebookのweb viewで/article/1を開ける
  • はてなブックマークして/article/1を開ける
  • mobile Safariのブックマークから/article/1を開ける

( ゚∀゚)o彡゜シェア!シェア!

8
Help us understand the problem. What is going on with this article?
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
studioTeaTwo
More features, more future.

Comments

No comments
Sign up for free and join this conversation.
Sign Up
If you already have a Qiita account Login
8
Help us understand the problem. What is going on with this article?