アイデアを思いついたときに Web サービスを作ってスタートアップする手順メモです。Rails 編としていますが、たぶん別のバージョンは書かないでしょう。。

手順は思いついた順(というか、前に作ったサービスの手順)で書いていますので、抜け漏れとかあると思いますがコメントあればよろしくおねがいします。

ちなみに、最近作ったサービスはこちら。よかったら使ってみてください。

Web上のメディアのすべてにヤジを飛ばすサービス - 野次る

1. アイデアを揉む

思いついたアイデアをすぐに実現するのはいいことだと思いますが、世の中では2パターンで語られていますね。

  1. 思いついたサービスは誰のためのサービス?
  2. そのサービスは自分で使う?

誰のためのサービスかという問は、すなわちどんな問題を解決するサービスなのか?ってことです。 「スタートアップが失敗する理由」みたいなキーワードで Google 先生に聞けばイロイロ教えてくれるかと。

まぁでも、思いついたサービスを自分で使ってみたいと思えるのなら創るべき でしょう!僕はそう思います。

2. プロジェクトを作成する

さて、実際にサービスをつくっていきます。まずは Rails のプロジェクトを作らねばなりません。 プロジェクトのスタートアップ手順は Qiita にメモを残していますのでそっちを参照してみてください。

Rails4プロジェクトを新規で立ち上げる時の設定メモ - Qiita

3. デザインを決める

デザインとか多少でもかじった人なんかはすんなり行くんでしょうけど、デザインだけはまるでダメっていう人もいると思います。 そんなときは、Bootstrap 使いましょう。

Bootstrap も Sass が提供されるようになりましたので、Sass ファイルをダウンロードしておくと、後でデザインを弄りたい時とか便利です。 ダウンロードした Sass ファイルは vendor/assets に入れましょう。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
- vendor
  - assets
    - fonts
      - bootstrap
        glyphicons-halflings-regular.eot
        glyphicons-halflings-regular.svg
        glyphicons-halflings-regular.ttf
        glyphicons-halflings-regular.woff
    - javascripts
      - bootstrap
        affix.js
        alert.js
        button.js
        carousel.js
        collapse.js
        dropdown.js
        modal.js
        popover.js
        scrollspy.js
        tab.js
        tooltip.js
        transition.js
      bootstrap.js
    - stylesheets
      - bootstrap
        _alerts.scss
        _badges.scss
        _breadcrumbs.scss
        _button-groups.scss
        _buttons.scss
        _carousel.scss
        _close.scss
        _code.scss
        _component-animations.scss
        _dropdowns.scss
        _forms.scss
        _glyphicons.scss
        _grid.scss
        _input-groups.scss
        _jumbotron.scss
        _labels.scss
        _list-group.scss
        _media.scss
        _mixins.scss
        _modals.scss
        _navbar.scss
        _navs.scss
        _normalize.scss
        _pager.scss
        _pagination.scss
        _panels.scss
        _popovers.scss
        _print.scss
        _progress-bars.scss
        _responsive-utilities.scss
        _scaffolding.scss
        _tables.scss
        _theme.scss
        _thumbnails.scss
        _tooltip.scss
        _type.scss
        _utilities.scss
        _variables.scss
        _wells.scss
        bootstrap.scss

app/assets/javascripts/application.jsapp/assets/stylesheets/application.css に bootstrap のファイルを追加します。

app/assets/javascripts/application.js
1
2
3
4
//= require jquery
//= require jquery_ujs
//= require bootstrap
//= require_self
app/assets/stylesheets/application.css
1
2
3
4
/*
 *= require bootstrap
 *= require_self
 */

僕は require tree は削除しちゃう派で、require が必要なファイルは自分で記述するようにしています。fonts フォルダは asset pipeline に含まれないので明示的に config/application.rb で読み込むようにします。

1
2
3
4
5
6
7
#...
module YourApplication
  class Application < Rails::Application
    # ...
    config.assets.paths << "#{Rails.root}/vendor/assets/fonts"
  end
end

4. テンプレートに BOILERPLATE を使う

デザインと順番が前後するかもしれませんが、最近はレスポンシブデザインがはやりですよね。 HTML5 でレスポンシブ対応となるとキマリ文句みたいなテンプレートがあるわけなんですが、いちいち調べる物めんどくさいので僕は BOILERPLATE を使っています。

HTML5 ★ BOILERPLATE - The web’s most popular front-end template

BOILERPLATE をダウンロードして、プロジェクトディレクトリに配置します。index.html の中身をごっそり app/views/layouts/application.html.erb にいれこみます。 このとき、csrf_meta_tags を間違って消さないように注意です。

BOILERPLATE を見ると分かるように、最近は JavaScript ファイルを body 部の最後に置くようになっています。DOM の構築をブロックしないようにするためですね。 で、Rails では turbolinks っていう機能があって、まぁいろいろとこの辺がバッティングしてウザイので、turbolinks を使わないようにします。

Rails 4 で turbolinks をオフにする方法 - Qiita

5. レイアウトファイルをいじる(SEO対策)

プログラムを書き始める前に、先にデザイン回りを調整しておくのがいいかと思います。

いろいろ追加すると大体こんな感じのテンプレートになります。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
<!DOCTYPE html>
<!--[if lt IE 7]>      <html class="no-js lt-ie9 lt-ie8 lt-ie7"> <![endif]-->
<!--[if IE 7]>         <html class="no-js lt-ie9 lt-ie8"> <![endif]-->
<!--[if IE 8]>         <html class="no-js lt-ie9"> <![endif]-->
<!--[if gt IE 8]><!--> <html class="no-js"> <!--<![endif]-->
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<title><%= content_for?(:title) ? "#{yield(:title)} - " : '' %><%= Settings.app_name %></title>
<meta name="viewport" content="width=device-width, initial-scale=1">

<meta name="keywords" content="<%= content_for?(:keywords) ? "#{yield(:keywords)}" : "#{Settings.keywords}" %>">
<meta name="description" content="<%= content_for?(:description) ? "#{yield(:description)}" : "#{Settings.description}" %>">

<meta property="og:title" content="<%= content_for?(:title) ? "#{yield(:title)} - " : '' %><%= Settings.app_name %>" />
<meta property="og:type" content="article" />
<meta property="og:url" content="<%= request.url %>" />
<meta property="og:site_name" content="<%= Settings.app_name %>" />
<meta property="og:description" content="<%= content_for?(:description) ? "#{yield(:description)}" : "#{Settings.description}" %>" />
<meta property="fb:app_id" content="<%= Settings.facebook.app_id %>" />

<%= stylesheet_link_tag    "normalize", media: "all" %>
<%= stylesheet_link_tag    "application", media: "all" %>
<%= javascript_include_tag 'modernizr-2.6.2.min' %>
<%= csrf_meta_tags %>
</head>
<body>

<header class="navbar navbar-default" role="banner">
  <div class="container">
    <div class="navbar-header">
      <a href="/" class="navbar-brand"><%= Settings.app_name %></a>
      <button class="navbar-toggle" type="button" data-toggle="collapse" data-target="#navbar-main">
        <span class="icon-bar"></span>
        <span class="icon-bar"></span>
        <span class="icon-bar"></span>
      </button>
    </div>
    <nav class="navbar-collapse collapse" id="navbar-main" rel="navigation">
      <ul class="nav navbar-nav">
        <li></li>
      </ul>
    </nav>
  </div>
</header>

<!--[if lt IE 7]>
    <p class="browsehappy">You are using an <strong>outdated</strong> browser. Please <a href="http://browsehappy.com/">upgrade your browser</a> to improve your experience.</p>
<![endif]-->

<div class="container" role="main">
<%= yield %>
</div>

<footer role="contentinfo">
  &copy; <%= Date.today.year %> <%= Settings.copyright %>
</footer>

<%= javascript_include_tag "application" %>
</body>
</html>

Settings クラスは rails_config という gem を使った設定ファイルを読み込むものです。 SEO 対策系の title タグや og タグ周りの設定を意識したほうがいいです。

サイトマップの生成もここで検討しておいたほうがいいでしょう。

meta-tagsとsitemap_generatorで始めるRails 4.0時代のSEO対策 - 酒と泪とRubyとRailsと

6. コア部分をつくり込む

やっとプログラミング開始です。コア部分を作りこんでいきます。ここは気合が続く限り一気に作ったほうがいいと思います。 僕の場合は、サービス開発は結構集中力とやる気の持続がポイントになってくるので。

やる気の部分は結構重要で、1ヶ月とかかけて作るようだと案外あきちゃったりするんですよね。。エラー表示のところとかもちゃんとつくっていきましょう。

RailsでBootstrapを使ったときにフォームエラーをキレイに表示する方法 - Qiita

7. 404ページと500ページを用意する

コア部分ができたら、404ページと500ページを用意します。 Rails にはこの辺をうまく扱う方法がありますのでそれを使います。

config/routes.rb の最後のルートに、次のルールを追加します。

1
match '*not_found' => 'application#render_404', via: :all

規定のルールにマッチしないルートは、application_controller#render_404 に飛ばされます。ここで404エラーの処理を記述します。 同じく、application_controller に500エラーをハンドリングする処理も記述します。

app/controllers/application_controller.rb`
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
class ApplicationController < ActionController::Base
  protect_from_forgery with: :exception

  unless Rails.configuration.consider_all_requests_local
    rescue_from Exception, with: :render_500
    rescue_from AbstractController::ActionNotFound, with: :render_404
    rescue_from ActionController::RoutingError, with: :render_404
    rescue_from ActiveRecord::RecordNotFound, with: :render_404
  end

  def render_404(exception = nil)
    if exception
      logger.warn "Rendering 404 with exception: #{exception.message}"
    end
    render template: 'errors/error_404', status: 404, layout: 'application', content_type: 'text/html'
  end

  def render_500(exception = nil)
    if exception
      logger.fatal "Rendering 500 with exception: #{exception.message}"
      logger.fatal request.env.map { |key, val| "#{key} => #{val}" }
      logger.fatal exception.backtrace.join("\n")
    end

    respond_to do |format|
      format.html { render template: 'errors/error_500', status: 500, layout: 'application', content_type: 'text/html' }
      format.json { render json: {message: exception.message}, status: 500 }
    end
  end
end

app/views/errors ディレクトリに error_404.html.erberror_500.html.erb を作成して、それぞれ中身を記述します。

8. ソーシャル流入用のボタン類と Google Analytics を配置する

作ったサービスをユーザに宣伝してもらえるようにソーシャル系のボタンを用意するのを忘れないようにします。 また、Google Analytics も忘れないように設定します。 Facebook はアプリIDが必要になりますので別途登録してアプリIDを入手しておきます。Google Analytics も同じくですね。

ちなみに、ソーシャル系サービスはいろいろありますが、Facebook、Twitter、はてな、Google+、Pocket あたりを僕は抑えています。

ソーシャル系サービスで配布されている JavaScript を非同期で読み込むための JavaScript を作成します。 忘れずに application.js にも追加しておきます。

app/assets/javascripts/sharing.js.erb
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
(function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){
(i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o),
m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m)
})(window,document,'script','//www.google-analytics.com/analytics.js','ga');

ga('create', '<%= Settings.google.analytics_tracking_id %>', 'hamasyou.com');
ga('send', 'pageview');

(function (w, d) {
  var j, s = d.getElementsByTagName('script')[0],
  a = function (u, i) {
    if (!d.getElementById(i)) {
      j = d.createElement('script');
      j.type = 'text/javascript';
      j.src = u;
      j.async = true;
      if (i) {j.id = i;}
      s.parentNode.insertBefore(j, s);
    }
  };
  a('//platform.twitter.com/widgets.js', 'twitter-wjs');
  a('//b.st-hatena.com/js/bookmark_button.js','hatebu-js');
  a('//apis.google.com/js/plusone.js','plusone-js');
  a('//connect.facebook.net/ja_JP/all.js#appId=<%= Settings.facebook.app_id %>&xfbml=1','facebook-jssdk');
  a('//widgets.getpocket.com/v1/j/btn.js?v=1','pocket-btn-js');
})(this, document);

ボタンを集めた部分テンプレートを用意します。

app/views/shared/_sharing.html.erb
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
<% cache url do %>
  <ul class="sharing list-inline">
    <li class="hatena">
      <a href="http://b.hatena.ne.jp/entry/<%= url %>"
         class="hatena-bookmark-button"
         rel="nofollow"
         data-hatena-bookmark-layout="standard-balloon"
      ><img src="http://b.st-hatena.com/images/entry-button/button-only.gif"
            alt="Add to Hatena Bookmark" /></a>
    </li>
    <div id="fb-root"></div>
    <li class="facebook">
      <div class="fb-like"
           data-share="false"
           data-layout="button_count"
           data-show-faces="false"
           data-font="verdana"
           data-href="<%= url %>"></div>
    </li>
    <li class="facebook_share">
      <div class="fb-share-button"
           data-type="button_count"
           data-font="verdana"
           data-href="<%= url %>"></div>
    </li>
    <li class="twitter">
      <a href="http://twitter.com/share"
         class="twitter-share-button"
         rel="nofollow"
         data-url="<%= url %>"
         data-via="<%= Settings.twitter.data_via %>"
         data-counturl="<%= url %>">Tweet</a>
    </li>
    <li class="googleplus">
      <div class="g-plusone"
           data-size="medium"
           data-href="<%= url %>"></div>
    </li>
    <li class="pocket">
      <a href="https://getpocket.com/save"
         class="pocket-btn" data-lang="en"
         data-save-url="<%= url %>"
         data-pocket-count="horizontal"
         data-pocket-align="left">Pocket</a>
    </li>
  </ul>
<% end %>

url 変数は部分テンプレートを読み込むときに渡すシェアしたいページの URL です。最後に CSS も用意しておきます。

app/assets/stylesheets/sharging.css.scss
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
.sharing {
  li {
    height: 23px;
    vertical-align: bottom;
  }
}
li.hatena {
}
li.twitter {
}
li.googleplus {
}
li.facebook {
}
.fb_iframe_widget > span {
  vertical-align: baseline !important;
}
li.facebook_share {
}
#fb-root { display: none; }
li.pocket {
}

ボタンの長さ等の調整が必要であれば、ここで調整します。app/assets/stylesheets/application.css に登録するのを忘れずに。

9. SCM にプロジェクトを保存する

ここまで来たら後はサーバに載っけて公開するだけですが、作ったプロジェクトはソースコードリポジトリに登録しておきましょう。 リポジトリサービスとしては GitHub が有名ですが、GitHub は無料で使う分には公開リポジトリしか作れません。 そこで、同じくリポジトリサービスで非公開リポジトリが作成できる BitBucket を使うといいと思います。

10. サービス公開用のサーバを用意する

最後にサービス公開用のサーバを用意するわけですが、いきなり AWS をつかうのもいいんですが AWS は個人で使うにはちょっとお高いんですよね。。 VPS を借りるなら Sakura VPS とかのほうが安くすみます。が、無料でサーバを利用したいのであれば Heroku がいいかと思います。

Rails4でheroku Pushまでの最短手順 [Haml/bootstrap 3.0/postgresql or MySQL] - 酒と泪とRubyとRailsと

サービスを大きくしていく

サービスが軌道に乗ってきたら機能追加したり、独自ドメインをとったりしてサービスを大きくしていくといいと思います。

大体こんな感じで僕はいつもサービスを作っています。ちなみに、最近作ったサービスはこちら。よかったら使ってやってください。

Web上のメディアのすべてにヤジを飛ばすサービス - 野次る