tag:crieit.net,2005:https://crieit.net/tags/Netlify/feed 「Netlify」の記事 - Crieit Crieitでタグ「Netlify」に投稿された最近の記事 2021-02-20T15:13:43+09:00 https://crieit.net/tags/Netlify/feed tag:crieit.net,2005:PublicArticle/16694 2021-02-20T15:13:23+09:00 2021-02-20T15:13:43+09:00 https://crieit.net/posts/Netlify-GitHub-Actions-0 Netlifyのビルド時間をGitHub Actionsで0時間にして月末のヒヤヒヤから解放されよう! <h1 id="Netlifyのビルド時間をGitHub Actionsで0時間にして月末のヒヤヒヤから解放されよう!"><a href="#Netlify%E3%81%AE%E3%83%93%E3%83%AB%E3%83%89%E6%99%82%E9%96%93%E3%82%92GitHub+Actions%E3%81%A70%E6%99%82%E9%96%93%E3%81%AB%E3%81%97%E3%81%A6%E6%9C%88%E6%9C%AB%E3%81%AE%E3%83%92%E3%83%A4%E3%83%92%E3%83%A4%E3%81%8B%E3%82%89%E8%A7%A3%E6%94%BE%E3%81%95%E3%82%8C%E3%82%88%E3%81%86%EF%BC%81">Netlifyのビルド時間をGitHub Actionsで0時間にして月末のヒヤヒヤから解放されよう!</a></h1> <h2 id="Netlify"><a href="#Netlify">Netlify</a></h2> <p>みなさんもご存じ超便利ありがたサービスNetlifyですが、<strong>無料で使ってる貧民</strong>には毎月とある悩みがでてきます。</p> <p><em>今月のビルド時間は残り○○分</em></p> <p><img src="https://i.imgur.com/TSm24w0.png" alt="img" /></p> <p>NetlifyはGitHubのレポジトリと連携して、フロントのビルドを実行した上で、デプロイするという超便利機能があるのですが、このビルドを回すのに時間の制約があり、</p> <p>無料民だと月300分となっております。(それ以上はPro版月19ドル課金すれば問題なく使えます。課金も経験済み)</p> <p>300分あれば大丈夫そう、とそう思う気もしなくなくもないですが、</p> <p>複数レポジトリにわたってNetlifyを使っていたり、Gatsby.jsで画像をたくさん使っていて<strong>Sharp</strong>の画像リサイズに時間がかかったり、<strong>Dependabot</strong>で定期的にPRが出てPreview deployが発生したりすると<br /> 案外ぎりぎりだったりします。</p> <p><img src="https://i.imgur.com/y7ixbEG.png" alt="img" /></p> <p>なので、私のような貧民は月末になると、Netlifyのビルド時間が気になって<strong>このブログの記事を書かなくなったり</strong>、<strong>サイトリファクターのペースが落ちて</strong>しまいます。</p> <p>特にブログ更新は顕著で、例えば今書いている記事も通勤の電車の中でスマホから書いているわけなので、細かくコミットを打って保存したいのですが、コミットを打ってプッシュしてしまうと、ビルドが走ることになるので、WIPでのコミットが億劫になり、結果的に家のようなまとめてプッシュできるような作業スペースがある場所でないと、<br /> ブログを書かなくなってしまいました。</p> <p>せっかく<a target="_blank" rel="nofollow noopener" href="https://blog.tubone-project24.xyz/2019-09-01-netlify-and-gatsby#cms%E3%81%AE%E7%AE%A1%E7%90%86%E7%94%BB%E9%9D%A2%E3%82%92%E8%A8%AD%E5%AE%9A%E3%81%99%E3%82%8B">Netlify CMS化</a>した意味がないですね。</p> <h2 id="この悩みGitHub Actionsにお任せください"><a href="#%E3%81%93%E3%81%AE%E6%82%A9%E3%81%BFGitHub+Actions%E3%81%AB%E3%81%8A%E4%BB%BB%E3%81%9B%E3%81%8F%E3%81%A0%E3%81%95%E3%81%84">この悩みGitHub Actionsにお任せください</a></h2> <p>ということでこの悩み、GitHub Actionsで解決してみたいと思います。</p> <p>なんか工務店のCMみたいな表現になってしまいました。</p> <p><img src="https://i.imgur.com/JlvUJ4zl.png" alt="ojisan" /></p> <h2 id="Netlifyのビルド時やっていることを洗い出して自前でやってみる"><a href="#Netlify%E3%81%AE%E3%83%93%E3%83%AB%E3%83%89%E6%99%82%E3%82%84%E3%81%A3%E3%81%A6%E3%81%84%E3%82%8B%E3%81%93%E3%81%A8%E3%82%92%E6%B4%97%E3%81%84%E5%87%BA%E3%81%97%E3%81%A6%E8%87%AA%E5%89%8D%E3%81%A7%E3%82%84%E3%81%A3%E3%81%A6%E3%81%BF%E3%82%8B">Netlifyのビルド時やっていることを洗い出して自前でやってみる</a></h2> <p>基本的にNetlifyがビルド時やってることは、例えばGatsby.jsであれば、gatsby buildコマンドを実行し、特定のディレクトリー(大概は./public)に配置されたビルド済みJSをデプロイする動きなので、<br /> それをそっくりGitHub Actionsに移行すればいいのですが、Netlifyがビルド済みJSに対して後処理(PostProcess)を実行してるパターンもあります。</p> <p>私の場合、JSやイメージを最適化してくれる<strong>Asset optimization</strong>とFormタグに属性をつければ勝手にFormを作ってくれる<strong>Form detection</strong>の二つが設定されていましたのでそれぞれまず無効化します。</p> <p>Form detectionの解説は<a target="_blank" rel="nofollow noopener" href="https://blog.tubone-project24.xyz/2019/09/30/netlify-form">こちら</a>を参照ください。</p> <p><img src="https://i.imgur.com/ytjbJQA.png" alt="img" /></p> <p><img src="https://i.imgur.com/LfL70Br.png" alt="img" /></p> <p>こちら、Netlifyで実施してくれなくなりますので、こちらで実装し直す必要があります。</p> <h2 id="gatsby-plugin-minify"><a href="#gatsby-plugin-minify">gatsby-plugin-minify</a></h2> <p>Asset optimizationのうち、JSやCSSのminiferは<a target="_blank" rel="nofollow noopener" href="https://www.gatsbyjs.com/plugins/gatsby-plugin-minify/">gatsby-plugin-minify</a>を使うことでhtmlやJS、CSSをminifyできます。</p> <p>インストールはいつも通りNPM(yarn)から</p> <pre><code>npm install gatsby-plugin-minify </code></pre> <p>使い方はgatsby-config.jsのpluginsに次のように設定すればできます。</p> <pre><code> { resolve: 'gatsby-plugin-minify', options: { caseSensitive: false, collapseBooleanAttributes: true, useShortDoctype: false, removeEmptyElements: false, removeComments: true, removeAttributeQuotes: false, minifyCSS: true, minifyJS: true, }, }, </code></pre> <p>minifyCSSとminifyJSをtrueにすることにより、CSSについては<a target="_blank" rel="nofollow noopener" href="https://github.com/jakubpawlowicz/clean-css">clean-css</a>、JSについては<a target="_blank" rel="nofollow noopener" href="https://github.com/mishoo/UglifyJS">UglifyJS</a>を使って一緒にminifyされます。また、gatsby-plugin-minifyの裏側は<a target="_blank" rel="nofollow noopener" href="https://github.com/kangax/html-minifier">html-minifier</a>をgatsby-node.jsでpostbuildで全掛けしているだけなので、細かいオプションは<a target="_blank" rel="nofollow noopener" href="https://github.com/kangax/html-minifier#options-quick-reference">html-minifier</a>で設定できる感じです。</p> <p>ちなみに、気を付けないといけないのが<strong>removeAttributeQuotes</strong>のオプションをfalseにすること。</p> <p>これをtrueにすると、HTMLタグ内のアトリビュートにダブルクオートが入らなくなりちょっとファイルが軽くなるのですが、<a target="_blank" rel="nofollow noopener" href="https://berss.com/feed/Find.aspx">berss.com</a>のようにサイトのRSSリンクを取得するようなシステムでうまく読み込めなくなってしまい、サイト更新が最悪通知できなくなってしまう現象が発生しました。</p> <p>これで1日使ってしまった...。</p> <p>RSSのリンクをページのLinkとして仕込んでいる人は要注意です。</p> <h2 id="imgurを使うことで、画像ホスティングとリサイズを同時にやっちゃう"><a href="#imgur%E3%82%92%E4%BD%BF%E3%81%86%E3%81%93%E3%81%A8%E3%81%A7%E3%80%81%E7%94%BB%E5%83%8F%E3%83%9B%E3%82%B9%E3%83%86%E3%82%A3%E3%83%B3%E3%82%B0%E3%81%A8%E3%83%AA%E3%82%B5%E3%82%A4%E3%82%BA%E3%82%92%E5%90%8C%E6%99%82%E3%81%AB%E3%82%84%E3%81%A3%E3%81%A1%E3%82%83%E3%81%86">imgurを使うことで、画像ホスティングとリサイズを同時にやっちゃう</a></h2> <p><a target="_blank" rel="nofollow noopener" href="https://imgur.com/">imgur</a>というサービスがあります。</p> <p>主にRedditとかGifをあげるための画像ホスティングサービスとして有名なのですが、こちらを使うことで簡単に画像のリサイズとホスティングを実現できるため、このブログではimgurを使ってます。</p> <p>画像URLの後ろに画像サイズに合わせたキーワードを入れることで実現できます。</p> <p>例えばこちらのURLの画像を</p> <pre><code>[https://i.imgur.com/Wfz9G0B.png](https://i.imgur.com/Wfz9G0B.png) </code></pre> <p>160x160にリサイズするには後ろに<strong>b</strong>をくっつけます。</p> <pre><code>[https://i.imgur.com/Wfz9G0Bb.png](https://i.imgur.com/Wfz9G0Bb.png) </code></pre> <p>これで、画像最適化も完了です。</p> <h2 id="getform.io"><a href="#getform.io">getform.io</a></h2> <p><a target="_blank" rel="nofollow noopener" href="https://getform.io/">Getform.io</a>はフォームのバックエンドを提供するすばらしいサービスです。</p> <p>便利なインテグレーションを使うには有料版が必要ですが、フォームに投稿されたら指定したメールアドレスに通知メール飛ばす、くらいのことであれば無料でできます。</p> <p>これで、NetlifyのForm detectionを置き換えていきます。</p> <p>まず、新しいフォームを作ると、FormのAction先URLが発行できます。</p> <p>Formの作り方は下記のブログにわかりやすく纏めてあったので参照いただければと思います。</p> <p><a target="_blank" rel="nofollow noopener" href="https://blog.nakamu.life/posts/getform-io">https://blog.nakamu.life/posts/getform-io</a></p> <p>さて、Formができたらチュートリアルに沿ってそのまま、FormタグのactionにこちらのURLを設定してもいいのですが、GetFormは無料版だと、<strong>Form投稿後のThanksページが設定</strong>できません。</p> <pre><code class="html"><!-- * Add your getform endpoint into "action" attribute * Set a unique "name" field * Start accepting submissions --> <form action="{getform-endpoint}" method="POST"> <input type="text" name="name"> <input type="email" name="email"> <button type="submit">Send</button> </form> </code></pre> <p><img src="https://i.imgur.com/sT5vhFE.png" alt="img" /></p> <p>まぁこれでも十分なのですが、せっかく<strong>React</strong>を使ってるので、裏側でgetform.ioのURLをPOST fetchしながら、actionsで定義した自分のThanks URLに飛ばすように指定しましょう。</p> <p>まずは、Formに<strong>onSubmit</strong>を設定します。</p> <p>```typescript{numberLines: 1}{5}<br /> <form name="contact" method="post" action="/thanks/" onSubmit={this.handleSubmit} ><br /> <label><br /> <span className="icon-user" /> Your name<br /><br /> <input type="text" name="name" className="form-control" maxLength="30" minLength="2" required placeholder="Enter your name" onChange={this.handleChange} /><br /> </label><br /> </p></p> <pre><code><br />そして、別途にonSubmitで発火する関数を定義します。 ```typescript handleSubmit(e) { e.preventDefault(); const form = e.target; fetch('https://getform.io/f/xxxxxxxxxxxxxxxxxxxxxxxxx', { method: 'POST', body: Contact.encode({ 'form-name': form.getAttribute('name'), ...this.state, }), }) } </code></pre> <p>Formの送信なので、fetchでは<a target="_blank" rel="nofollow noopener" href="https://developer.mozilla.org/ja/docs/Web/API/FormData">FormData</a>に要素をappendしたものを送信しないといけません。</p> <pre><code> static encode(data) { const formData = new FormData(); // eslint-disable-next-line no-restricted-syntax for (const key of Object.keys(data)) { formData.append(key, data[key]); } return formData; } </code></pre> <p>繰り返しになりますがReactではFormで、actionのほか、onSubmitを関数としてすることができます。</p> <p>ただし、onSubmitが押されたタイミングで、Formの入力項目をPOST Fetchで渡さないといけないので、Formの入力で発生するchangeEventごとに、Formの値をstateとして保存しておくようにします。</p> <p>```typescript{numberLines: 1}{1-7,21}<br /> handleChange(e) {<br /> this.setState({ [e.target.name]: e.target.value });<br /> }</p> <p>handleAttachment(e) {<br /> this.setState({ [e.target.name]: e.target.files[0] });<br /> }</p> <p>(中略)</p> <pre><code> <label> <span className="icon-user" />&nbsp;Your name<br /> <input type="text" name="name" className="form-control" maxLength="30" minLength="2" required placeholder="Enter your name" onChange={this.handleChange} /> </label> </p> </code></pre> <pre><code><br />また、onSubmitを使ってしまうと、Form規定のactionでは飛ばなくなるので自前でGatsbyのnavigateを使ってPost処理が終わったらThanksページに飛ぶようにします。 ```typescript{numberLines: 1}{11-12} handleSubmit(e) { e.preventDefault(); const form = e.target; fetch('https://getform.io/f/897f187e-876d-42a7-b300-7c235af72e6d', { method: 'POST', body: Contact.encode({ 'form-name': form.getAttribute('name'), ...this.state, }), }) .then(() => navigateTo(form.getAttribute('action'))) .catch((error) => alert(error)); } </code></pre> <p>これでGetForm無料版でも自前のThanksページを作ることができます。</p> <p><img src="https://i.imgur.com/gumRkbF.png" alt="img" /></p> <h2 id="GitHub Actionsでビルドとデプロイ"><a href="#GitHub+Actions%E3%81%A7%E3%83%93%E3%83%AB%E3%83%89%E3%81%A8%E3%83%87%E3%83%97%E3%83%AD%E3%82%A4">GitHub Actionsでビルドとデプロイ</a></h2> <p>ここまで来たらあとはGitHub Actionsでビルドとデプロイを行うだけです。</p> <p>masterブランチへのPRでPreviewデプロイ、masterへのコミットで本番デプロイをするように2つactionsを作ります。</p> <p>まずはPreviewデプロイ</p> <pre><code class="yaml">name: DeployToNetlifyPreview on: pull_request: branches: - master jobs: build: runs-on: ubuntu-latest steps: - name: Checkout source code uses: actions/checkout@v2 - name: Cache node_modules uses: actions/cache@v1 with: path: node_modules key: $<span>{</span><span>{</span> runner.OS <span>}</span><span>}</span>-build-$<span>{</span><span>{</span> hashFiles('**/package-lock.json') <span>}</span><span>}</span> restore-keys: | $<span>{</span><span>{</span> runner.OS <span>}</span><span>}</span>-build- $<span>{</span><span>{</span> runner.OS <span>}</span><span>}</span> - name: Setup Node uses: actions/setup-node@v1 with: node-version: 12.x - name: npm install and build env: GATSBY_GITHUB_CLIENT_SECRET: $<span>{</span><span>{</span>secrets.GATSBY_GITHUB_CLIENT_SECRET<span>}</span><span>}</span> GATSBY_GITHUB_CLIENT_ID: $<span>{</span><span>{</span>secrets.GATSBY_GITHUB_CLIENT_ID<span>}</span><span>}</span> GATSBY_ALGOLIA_SEARCH_API_KEY: $<span>{</span><span>{</span>secrets.GATSBY_ALGOLIA_SEARCH_API_KEY<span>}</span><span>}</span> GATSBY_ALGOLIA_INDEX_NAME: $<span>{</span><span>{</span>secrets.GATSBY_ALGOLIA_INDEX_NAME<span>}</span><span>}</span> GATSBY_ALGOLIA_APP_ID: $<span>{</span><span>{</span>secrets.GATSBY_ALGOLIA_APP_ID<span>}</span><span>}</span> GATSBY_ALGOLIA_ADMIN_API_KEY: $<span>{</span><span>{</span>secrets.GATSBY_ALGOLIA_ADMIN_API_KEY<span>}</span><span>}</span> FAUNADB_SERVER_SECRET: $<span>{</span><span>{</span>secrets.FAUNADB_SERVER_SECRET<span>}</span><span>}</span> run: | npm install npm run build - name: Deploy to netlify run: npx netlify-cli deploy --dir=./public > cli.txt env: NETLIFY_AUTH_TOKEN: $<span>{</span><span>{</span> secrets.NETLIFY_AUTH_TOKEN <span>}</span><span>}</span> NETLIFY_SITE_ID: $<span>{</span><span>{</span> secrets.NETLIFY_SITE_ID <span>}</span><span>}</span> - name: Cat cli.txt run: | cat cli.txt sed -i -z 's/\n/\\n/g' cli.txt - name: Post Netlify CLI Comment env: GITHUB_TOKEN: $<span>{</span><span>{</span> secrets.GITHUB_TOKEN <span>}</span><span>}</span> URL: $<span>{</span><span>{</span> github.event.pull_request.comments_url <span>}</span><span>}</span> run: | curl -X POST \ -H "Authorization: token ${GITHUB_TOKEN}" \ -d "{\"body\": \"$(cat cli.txt)\"}" \ ${URL} </code></pre> <p>node setupやnpm install, buildはいつも通りです。</p> <p>GitHub ActionsではSecretを指定することができますので、Algolia searchやFaunaDBのAPIキーはシークレットとしてビルド時の環境変数で渡してます。</p> <p>ちなみに、環境変数で<strong>GATSBY_XXXX</strong>としておくと、ビルドされたJSにも環境変数が入る形になります。(JSから環境変数を使う場合はこれを忘れないこと。)これ結構詰まるポイント。</p> <p>デプロイには<a target="_blank" rel="nofollow noopener" href="https://docs.netlify.com/cli/get-started/">netlify-cli</a>を使います。</p> <p>必要な環境変数はサイトIDとAUTH TOKENです。</p> <p>ちょっと特徴として、netlify-cliでデプロイが成功すると、<strong>デプロイURLが標準出力</strong>に出ますので、それをいったん適当なtextファイルに書き出し、</p> <p>PRコメントにもURLを送るようにしています。</p> <p>GitHub Actionsの素晴らしいところは、GITHUB TOKENについては、特に設定しなくてもsecrets.GITHUB_TOKENで取り出すことができますので簡単にPRコメントに送信できます。</p> <pre><code class="yaml"> - name: Deploy to netlify run: npx netlify-cli deploy --dir=./public > cli.txt env: NETLIFY_AUTH_TOKEN: $<span>{</span><span>{</span> secrets.NETLIFY_AUTH_TOKEN <span>}</span><span>}</span> NETLIFY_SITE_ID: $<span>{</span><span>{</span> secrets.NETLIFY_SITE_ID <span>}</span><span>}</span> - name: Cat cli.txt run: | cat cli.txt sed -i -z 's/\n/\\n/g' cli.txt - name: Post Netlify CLI Comment env: GITHUB_TOKEN: $<span>{</span><span>{</span> secrets.GITHUB_TOKEN <span>}</span><span>}</span> URL: $<span>{</span><span>{</span> github.event.pull_request.comments_url <span>}</span><span>}</span> run: | curl -X POST \ -H "Authorization: token ${GITHUB_TOKEN}" \ -d "{\"body\": \"$(cat cli.txt)\"}" \ ${URL} </code></pre> <p>次に本番へのデプロイです。</p> <pre><code class="yaml">name: DeployToNetlifyPRD on: push: branches: - master jobs: build: runs-on: ubuntu-latest steps: - name: Checkout source code uses: actions/checkout@v2 - name: Cache node_modules uses: actions/cache@v1 with: path: node_modules key: $<span>{</span><span>{</span> runner.OS <span>}</span><span>}</span>-build-$<span>{</span><span>{</span> hashFiles('**/package-lock.json') <span>}</span><span>}</span> restore-keys: | $<span>{</span><span>{</span> runner.OS <span>}</span><span>}</span>-build- $<span>{</span><span>{</span> runner.OS <span>}</span><span>}</span> - name: Setup Node uses: actions/setup-node@v1 with: node-version: 12.x - name: npm install and build env: GATSBY_GITHUB_CLIENT_SECRET: $<span>{</span><span>{</span>secrets.GATSBY_GITHUB_CLIENT_SECRET<span>}</span><span>}</span> GATSBY_GITHUB_CLIENT_ID: $<span>{</span><span>{</span>secrets.GATSBY_GITHUB_CLIENT_ID<span>}</span><span>}</span> GATSBY_ALGOLIA_SEARCH_API_KEY: $<span>{</span><span>{</span>secrets.GATSBY_ALGOLIA_SEARCH_API_KEY<span>}</span><span>}</span> GATSBY_ALGOLIA_INDEX_NAME: $<span>{</span><span>{</span>secrets.GATSBY_ALGOLIA_INDEX_NAME<span>}</span><span>}</span> GATSBY_ALGOLIA_APP_ID: $<span>{</span><span>{</span>secrets.GATSBY_ALGOLIA_APP_ID<span>}</span><span>}</span> GATSBY_ALGOLIA_ADMIN_API_KEY: $<span>{</span><span>{</span>secrets.GATSBY_ALGOLIA_ADMIN_API_KEY<span>}</span><span>}</span> FAUNADB_SERVER_SECRET: $<span>{</span><span>{</span>secrets.FAUNADB_SERVER_SECRET<span>}</span><span>}</span> run: | npm install npm run build - name: Deploy to netlify run: npx netlify-cli deploy --prod --dir=./public env: NETLIFY_AUTH_TOKEN: $<span>{</span><span>{</span> secrets.NETLIFY_AUTH_TOKEN <span>}</span><span>}</span> NETLIFY_SITE_ID: $<span>{</span><span>{</span> secrets.NETLIFY_SITE_ID <span>}</span><span>}</span> </code></pre> <p>ほとんど同じですが、netlify-cliでdeployコマンドに --prodオプションを入れることで、本番環境へデプロイされます。</p> <pre><code class="yaml"> - name: Deploy to netlify run: npx netlify-cli deploy --prod --dir=./public env: NETLIFY_AUTH_TOKEN: $<span>{</span><span>{</span> secrets.NETLIFY_AUTH_TOKEN <span>}</span><span>}</span> NETLIFY_SITE_ID: $<span>{</span><span>{</span> secrets.NETLIFY_SITE_ID <span>}</span><span>}</span> </code></pre> <h2 id="結論"><a href="#%E7%B5%90%E8%AB%96">結論</a></h2> <p>これで、Netlifyのビルド時間は0になり、精神的に安心できるようになりました。</p> <p><img src="https://i.imgur.com/ugdUr9l.png" alt="img" /></p> <p>リファクタや記事の執筆もはかどっていいですね!!</p> tubone24 tag:crieit.net,2005:PublicArticle/16336 2020-12-10T20:50:51+09:00 2021-03-16T19:10:22+09:00 https://crieit.net/posts/410-5fd20b9bead54 いつかサーバーを閉じるとき 〜お金をかけずに 410 を返す方法〜 <p>この記事は <a target="_blank" rel="nofollow noopener" href="https://adventar.org/calendars/6046">Fediverse Advent Calendar 2020</a> 9日目の記事です。</p> <p>昨日の記事は、Fediverse でおなじみのコーヒー豆屋さんNelson Coffee Roasterさんの仙台でおすすめのお店記事でした。<br /> Nelsonさんとこからは、僕も何度か豆を買わせていただいてます。仙台行ってみたい。<br /> <a target="_blank" rel="nofollow noopener" href="https://note.com/ncr/n/n852581e69a46">Fediverseでお世話になっている豆屋です|まめや|note</a></p> <p>今日の記事は、サーバーを閉じる時のお話です。<br /> ActivityPub サーバーをやってきたけど、いろんな理由で閉じたい… と言うことがあると思います。<br /> 連合しているサーバーにできるだけ影響を与えず、かつ可能な限りお金をかけずにサーバーを閉じる方法を書きます。</p> <p>僕は Mastodon サーバーしか運営していないので未確認ですが、他の ActivityPub 対応サーバーでも使える手だと思います。</p> <h2 id="tl;dr"><a href="#tl%3Bdr">tl;dr</a></h2> <ul> <li>Netlify を使って、無料で 410 Gone を返すヤツを作った</li> <li>tootctl self-destruct はどう動くか知らん</li> </ul> <h2 id="410 を返せとは言われるけど"><a href="#410+%E3%82%92%E8%BF%94%E3%81%9B%E3%81%A8%E3%81%AF%E8%A8%80%E3%82%8F%E3%82%8C%E3%82%8B%E3%81%91%E3%81%A9">410 を返せとは言われるけど</a></h2> <p>サーバーを閉じたいと誰かに相談すれば、だいたい「しばらくの間は410 Goneを返した方がいい」と言われるわけですが、410 Goneを返すにもサーバーが必要なわけで、特に経済的な理由で閉じたいとき解決になってない、という話があります。</p> <p>410 Gone をお金をかけずに返す方法があれば、そういう苦難から解放されるわけです。</p> <p>それ、Netlify を使えば無料で出来ます。</p> <h2 id="無料で 410 Gone を返すヤツを作る"><a href="#%E7%84%A1%E6%96%99%E3%81%A7+410+Gone+%E3%82%92%E8%BF%94%E3%81%99%E3%83%A4%E3%83%84%E3%82%92%E4%BD%9C%E3%82%8B">無料で 410 Gone を返すヤツを作る</a></h2> <p>Netlify という静的なサイトを無料でホスティングするサービスがあります。<br /> こいつを活用すると、閉鎖したサーバーに来たリクエストに対して 410 Goneを返すことができます。しかも<strong>無料</strong>です。<br /> デプロイするだけでそれができるリポジトリを作りました。<br /> <a target="_blank" rel="nofollow noopener" href="https://github.com/cyber-gene/netlify-410">cyber-gene/netlify-410</a></p> <h3 id="使い方"><a href="#%E4%BD%BF%E3%81%84%E6%96%B9">使い方</a></h3> <ol> <li><p>リポジトリをフォークしましょう。<br /> Netlify にデプロイするには、自分のリポジトリである必要があります。</p></li> <li><p>Netlify のアカウントを持ってなければ作成する<br /> 有料プランもありますが、今回の用途であれば無料の STARTER で問題ないと思います。<br /> <a target="_blank" rel="nofollow noopener" href="https://www.netlify.com/">Netlify</a></p></li> <li><p>サイトを作る<br /> New site from Git を押す<br /> <a href="https://crieit.now.sh/upload_images/9ab377531ab3126d06ef6296ee2795cf5fd2077711a40.png" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/9ab377531ab3126d06ef6296ee2795cf5fd2077711a40.png?mw=700" alt="netlify-overview.png" /></a><br /> GitHub 等のアカウントと連携させる画面が出てきます。Forkしたリポジトリがあるサービスを選択して連携させます。<br /> <a href="https://crieit.now.sh/upload_images/ea01dd6e0dc74e5f7a096d9c9dd01f185fd20799358d9.png" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/ea01dd6e0dc74e5f7a096d9c9dd01f185fd20799358d9.png?mw=700" alt="netlify-step1.png" /></a><br /> 連携させると、リポジトリを選択するのが出てくるので、netlify-410 を選択<br /> <a href="https://crieit.now.sh/upload_images/0fb23782be8272ab15a6689ee3ff45a35fd207c546b86.png" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/0fb23782be8272ab15a6689ee3ff45a35fd207c546b86.png?mw=700" alt="netlify-step2.png" /></a><br /> ビルド設定はそのままで動きます。Deploy siteを押すとサイトができます。<br /> <a href="https://crieit.now.sh/upload_images/79e9f828e38904172fac66c7cf8c4c5e5fd207eadef08.png" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/79e9f828e38904172fac66c7cf8c4c5e5fd207eadef08.png?mw=700" alt="netlify-step3.png" /></a></p></li> <li><p>ドメインの設定をする<br /> Site setting → Domain management → Custom domains からドメインを追加します。<br /> 画面で指定されたDNSレコードを追加し、ドメインへのアクセスがNetlifyに向かうように設定します。<br /> それをNetlify側が確認できたら、SSLの証明書とかを勝手に作ってくれます。らくちん!<br /> ※ 自分のドメインでアクセスできるようになるまでしばらく時間がかかります。気長に待ちましょう。<br /> このへんが全部終わったら、Webブラウザからアクセスしてみましょう。<br /> こんな画面が出ていればOKです!<br /> <a href="https://crieit.now.sh/upload_images/d6f7c84d1aeb3028aea1bc12ca37a0545fd208010bfc7.png" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/d6f7c84d1aeb3028aea1bc12ca37a0545fd208010bfc7.png?mw=700" alt="netlify-step4.png" /></a></p></li> </ol> <h2 id="本当に410返してんの?"><a href="#%E6%9C%AC%E5%BD%93%E3%81%AB410%E8%BF%94%E3%81%97%E3%81%A6%E3%82%93%E3%81%AE%EF%BC%9F">本当に410返してんの?</a></h2> <p>Webブラウザの開発者ツールを使って、本当に410を返してるのか見てみましょう。ステータスが410になっていますね?<br /> <a href="https://crieit.now.sh/upload_images/70244be269c5d227f7d3022f18a5af2d5fd20811ea6c7.png" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/70244be269c5d227f7d3022f18a5af2d5fd20811ea6c7.png?mw=700" alt="netlify-confirm-1.png" /></a></p> <p>他のサーバーから配信を受ける /inbox も見てみましょう。410なのでオッケー。<br /> <a href="https://crieit.now.sh/upload_images/197ffdb91ab6c414648413f02a4ed3315fd2081fcfd23.png" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/197ffdb91ab6c414648413f02a4ed3315fd2081fcfd23.png?mw=700" alt="netlify-confirm-2.png" /></a></p> <p>これで、410を無料で連合しているサーバーに返すことができます。</p> <h2 id="で、いつまで410返したらいいの?"><a href="#%E3%81%A7%E3%80%81%E3%81%84%E3%81%A4%E3%81%BE%E3%81%A7410%E8%BF%94%E3%81%97%E3%81%9F%E3%82%89%E3%81%84%E3%81%84%E3%81%AE%EF%BC%9F">で、いつまで410返したらいいの?</a></h2> <p>ドメインの有効期限があるうちは、410返しとけばいいと思います。<br /> ドメインを更新してまで410を返すべきとは、僕は思いません。無理の無い範囲でやっていけばいいと思います。</p> <h2 id="おわりに"><a href="#%E3%81%8A%E3%82%8F%E3%82%8A%E3%81%AB">おわりに</a></h2> <p>自分でサーバーを立てることは自由なので、閉じることも自由であるはずですが、他のサーバと密に連携して動いている関係上、サーバーを閉じると少なからず他のサーバーに影響を与えてしまいます。<br /> が、この方法を使えば、少なくともドメインを維持している間は追加費用をかけずに、他のサーバーへの影響を最小限にして自分のサーバーを閉じることができます。<br /> このへんは、いざサーバーを閉じるとなった時に二の足を踏む要因ではあるので、活用してみてください。</p> <p>一度閉じてしまうと、同じドメインで ActivityPub のサーバーをもう一度立ち上げようとしても、うまく連合に参加できないことがあります。閉じる時はそのへんの覚悟を持ちましょう。</p> <p>明日はオカどん管理人の kuloma1108 さんの記事です。</p> cybergene tag:crieit.net,2005:PublicArticle/15899 2020-05-16T23:29:13+09:00 2020-05-16T23:29:13+09:00 https://crieit.net/posts/NextJS アニメのレコメンドサービス、NextJS化しました。 <p>先週<a href="https://crieit.net/posts/b3c547c34df6393a1f86f8072aaf510a">アニメのレコメンドサービス</a>を作ったばかりなのですが、<br /> 業務で<strong>Next.js</strong>と<strong>React hooks</strong>が必要になり、<br /> 勉強がてらソースコードの少ないものをリメイクしてみました。</p> <h1 id="サービスのURL"><a href="#%E3%82%B5%E3%83%BC%E3%83%93%E3%82%B9%E3%81%AEURL">サービスのURL</a></h1> <p><a target="_blank" rel="nofollow noopener" href="https://annict-suggest.netlify.app/">Annictさじぇすと!</a></p> <h1 id="Next.js"><a href="#Next.js">Next.js</a></h1> <p>Next.jsはReactJSを用いたフレームワークです。<br /> <a target="_blank" rel="nofollow noopener" href="https://bagelee.com/programming/next-js/setup-next-js/">Next.jsの環境構築【これからはじめるNext.js】</a></p> <p>Next.jsのメリットとしては読み込むファイルが軽量になった分パフォーマンスが向上することでしょうか。</p> <h2 id="環境構築"><a href="#%E7%92%B0%E5%A2%83%E6%A7%8B%E7%AF%89">環境構築</a></h2> <p>今回環境構築の参考にしたのは<a target="_blank" rel="nofollow noopener" href="https://future-architect.github.io/typescript-guide/reactenv.html">こちら</a></p> <pre><code>(プロジェクトルート)  ├ components (コンポーネント類)  ├ context (React contextを格納するフォルダ)  ├ hooks (React hooksを格納するフォルダ)  ├ lib  │ └ gtag.js (analytics)  ├ pages  │ ├ _app.tsx (レイアウト)  │ ├ _document.tsx (index.htmlに相当)  │ ├ index.tsx  │ └ works  │   └ [id].tsx (idは可変なのでdynamic routes)  ├ public  │ └ images  └ styles (CSS) </code></pre> <h2 id="pages"><a href="#pages">pages</a></h2> <p>ファイルシステムの構造がそのままWEBシステムのパスになります。<br /> 例外として、<code>_app.tsx</code>と<code>_document.tsx</code>があります。</p> <h3 id="_app.tsx"><a href="#_app.tsx">_app.tsx</a></h3> <p>レイアウトを担当。</p> <pre><code class="js">const App = ({Component,pageProps}:AppProps) => { Router.events.on('routeChangeComplete', url => gtag.pageview(url)); return( <storeContext.Provider value={useSeasonName()}> <Layout> <Component {...pageProps} /> </Layout> </storeContext.Provider> ); } </code></pre> <h3 id="_document.tsx"><a href="#_document.tsx">_document.tsx</a></h3> <p>いわゆるindex.htmlに相当。<br /> あれ、これclass構文だったわ。(コピペ)</p> <pre><code class="js">class MyDocument extends Document<Props> { render() { return( <Html lang="ja-JP"> <Head>  <title></title> </Head> <body> <Main /> <NextScript /> </body> </Html> ) } } export default MyDocument; </code></pre> <h3 id="index.tsx"><a href="#index.tsx">index.tsx</a></h3> <pre><code class="js">export interface Work{ image:{ recommendedImageUrl:string }, twitterUsername:string, annictId:number, watchersCount:number, title:string, seasonYear:string, seasonName:string, wikipediaUrl:string, syobocalTid:string } const Index:NextPage = () => { const [pageNum,setPageNum] = useState(1); const [data,setData] = useState<Work[]>(); const store = useContext(storeContext); useEffect(() => { const f = async()=>{ let resData; if(store.query.length === 0){ resData = await fetchSeasonWorks(store.seasonName); }else{ resData = await fetchByTitle(store.query); } setData(resData); console.log(resData); } f(); },[store.seasonName,store.query]); const myRef = useRef<HTMLInputElement>(null); .... </code></pre> <h2 id="hooks"><a href="#hooks">hooks</a></h2> <p>hooksを格納する....はずなのですが、ビューとロジック分離するの失敗してるので<br /> またどこかで使うでしょう(笑)</p> <h2 id="context"><a href="#context">context</a></h2> <p>今回はreduxの代わりにcontextで全体の状態を管理します。<br /> <a target="_blank" rel="nofollow noopener" href="https://qiita.com/ragnar1904/items/0a4338523863922952bb">【React + Typescript】useContext の値を子コンポーネントから更新</a></p> <pre><code class="js">type StoreContext = { seasonName: string; query:string; setSeasonName: (seasonName: string) => void; setQuery: (query: string) => void; }; const defaultContext: StoreContext = { seasonName: CURRENT_SEASON, query:'', setSeasonName: () => {}, setQuery: () => {}, }; export const storeContext = createContext<StoreContext>(defaultContext); export const useSeasonName = (): StoreContext => { // state名はThemeContext typeのプロパティに合わせる。 const [seasonName, setSeason] = useState(CURRENT_SEASON); const [query, setQ] = useState(''); // 関数名はThemeContext typeのプロパティに合わせる。 const setSeasonName = useCallback((current: string): void => { setSeason(current); }, []); const setQuery = useCallback((current: string): void => { setQ(current); }, []); return { seasonName, query, setSeasonName, setQuery }; }; </code></pre> <p>個々のコンポーネントでも状態を持っています。</p> <h2 id="gtag"><a href="#gtag">gtag</a></h2> <p><a target="_blank" rel="nofollow noopener" href="https://qiita.com/b95oss/items/0402d2a0fa0edeecb67c">Next.jsでGoogle Analyticsを適用する</a></p> <h1 id="lintに苦戦..."><a href="#lint%E3%81%AB%E8%8B%A6%E6%88%A6...">lintに苦戦...</a></h1> <p>がっつりtypescriptを書いていたわけではなかったので、<br /> nextjsはなかなかビルドを通してくれませんでした。<br /> <a target="_blank" rel="nofollow noopener" href="https://github.com/zeit/next.js/issues/10284">ParsedUrlQuery typings should allow undefined values</a></p> <pre><code class="js">function isString(value: unknown): value is string { return typeof value === "string" } const id = isString(router.query.id) ? router.query.id : "" useEffect(()=>{ const f = async () => { if(router !== undefined && router.query !== undefined){ const resData = await fetchByWorkId(id); setData(resData); } } f(); },[router.query]); </code></pre> <h1 id="netlifyにデプロイ"><a href="#netlify%E3%81%AB%E3%83%87%E3%83%97%E3%83%AD%E3%82%A4">netlifyにデプロイ</a></h1> <ul> <li><a target="_blank" rel="nofollow noopener" href="https://qiita.com/mottox2/items/29fbb55129f7c41f1ae6">Next.jsのDynamic Routing + Static HTML exportを組み合わせて使う</a></li> <li><a target="_blank" rel="nofollow noopener" href="https://docs.netlify.com/routing/redirects/#syntax-for-the-netlify-configuration-file">Redirects and rewrites</a></li> </ul> <p><code>netlify.toml</code>に必要なルーティングを記述します。</p> <pre><code>[build] base = "/" # next exportの出力先 publish = "out/" # next buildからのnext exportでファイル生成 command = "yarn run build && yarn run export" [context.production] command = "NODE_ENV=production yarn run build && yarn run export" [[redirects]] from = "/" to = "/index.html" status = 200 [[redirects]] from = "/works/*" to = "/works/[id].html" status = 200 </code></pre> ckoshien tag:crieit.net,2005:PublicArticle/15888 2020-05-08T19:25:38+09:00 2020-05-08T19:25:38+09:00 https://crieit.net/posts/b3c547c34df6393a1f86f8072aaf510a アニメのレコメンドサービスを作りました。 <p><a href="https://crieit.now.sh/upload_images/b0f24117575660588f657c26c91d370e5eb525f79a7b2.png" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/b0f24117575660588f657c26c91d370e5eb525f79a7b2.png?mw=700" alt="" /></a></p> <h1 id="サービスURL"><a href="#%E3%82%B5%E3%83%BC%E3%83%93%E3%82%B9URL">サービスURL</a></h1> <p><a target="_blank" rel="nofollow noopener" href="https://annict-suggest.netlify.app/">https://annict-suggest.netlify.app/</a></p> <p><a href="https://crieit.now.sh/upload_images/102e92a702e89059033b1dff5b0f87c55eb5336deb10d.jpg" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/102e92a702e89059033b1dff5b0f87c55eb5336deb10d.jpg?mw=700" alt="" /></a></p> <h1 id="アニメの類似性をどう計算するか"><a href="#%E3%82%A2%E3%83%8B%E3%83%A1%E3%81%AE%E9%A1%9E%E4%BC%BC%E6%80%A7%E3%82%92%E3%81%A9%E3%81%86%E8%A8%88%E7%AE%97%E3%81%99%E3%82%8B%E3%81%8B">アニメの類似性をどう計算するか</a></h1> <h2 id="コサイン類似度"><a href="#%E3%82%B3%E3%82%B5%E3%82%A4%E3%83%B3%E9%A1%9E%E4%BC%BC%E5%BA%A6">コサイン類似度</a></h2> <p>人工知能を使わずにアニメのレコメンドサービスを作ろうと思ったのがきっかけです。<br /> <a href="https://crieit.net/posts/5308d8a3ed140ecc15e1310dad28e9e9">ユークリッド距離は触ったことがある</a>ので、他の指標としてコサイン類似度が面白そうだと思いました。<br /> ユークリッド距離は2点間の距離、コサイン類似度は2点のベクトル同士の角度です。</p> <ul> <li><a target="_blank" rel="nofollow noopener" href="https://www.albert2005.co.jp/knowledge/data_mining/cluster/cluster_summary">クラスター分析の手法①(概要)</a></li> <li><a target="_blank" rel="nofollow noopener" href="https://qiita.com/tetsutaroendo/items/61942d25ae2a017831f2">コサイン類似度を利用し、集団の類似性を測ってみる</a></li> </ul> <h3 id="使った指標"><a href="#%E4%BD%BF%E3%81%A3%E3%81%9F%E6%8C%87%E6%A8%99">使った指標</a></h3> <p>約3300の作品に対して「見た」「見てない」のベクトルを作ってコサイン類似度を算出しようとしました。<br /> 「あにこれ」のように成分分析されているタグの類似度を計算するのもありだと思いました。</p> <h3 id="挫折"><a href="#%E6%8C%AB%E6%8A%98">挫折</a></h3> <p>導入は比較的楽なように思えたのですが、計算量が尋常ではありませんでした。事前にフィルタリングを何もかけていなかったため、3300レコードx3300レコードの計算をしようとしていて、あまりに時間がかかるので諦めました。</p> <ul> <li><a target="_blank" rel="nofollow noopener" href="https://qiita.com/harperfu6/items/3238d8f78c8a8d8cf114">アイテムの類似性について考察してみる</a></li> </ul> <h3 id="結局SQL"><a href="#%E7%B5%90%E5%B1%80SQL">結局SQL</a></h3> <p>例:ID4342の作品を見たユーザを抽出して、<br /> それらのユーザが他に見た作品のうち共通している人数が多い順に30件を取得する。</p> <pre><code class="sql">select sum(st2.watch_status) as count, st2.work_id, w.title from status st2 -- ID:4342の作品を見たユーザを取得 inner join ( select st.user_id from status st where st.work_id = 4342 )st3 on st2.user_id = st3.user_id inner join works w on w.annict_id = st2.work_id -- 作品自身を除く where st2.work_id != 4342 group by st2.work_id order by count desc limit 30 </code></pre> <h1 id="今回使った技術"><a href="#%E4%BB%8A%E5%9B%9E%E4%BD%BF%E3%81%A3%E3%81%9F%E6%8A%80%E8%A1%93">今回使った技術</a></h1> <ul> <li>GraphQL(Annict API)</li> <li>ReactJS</li> <li>NodeJS(TypeScript)</li> <li>twitterAPI</li> <li>netlify</li> </ul> <h2 id="データの棲み分け"><a href="#%E3%83%87%E3%83%BC%E3%82%BF%E3%81%AE%E6%A3%B2%E3%81%BF%E5%88%86%E3%81%91">データの棲み分け</a></h2> <p>最新のデータが欲しい場合はAnnictAPI(GraphQL)、分析データが欲しい場合はDBから読み込み、というようにデータの棲み分けを行っています。</p> <h2 id="GraphQLでエイリアスを使う"><a href="#GraphQL%E3%81%A7%E3%82%A8%E3%82%A4%E3%83%AA%E3%82%A2%E3%82%B9%E3%82%92%E4%BD%BF%E3%81%86">GraphQLでエイリアスを使う</a></h2> <p><a target="_blank" rel="nofollow noopener" href="https://developers.annict.jp/graphql-api/reference/">Annict API</a><br /> ユーザが見たアニメと見ているアニメ両方が欲しい場合、<br /> エイリアスを使うと複数条件が記述できる。</p> <pre><code class="javascript">query { user(username:"${username}"){ annictId, works(state:WATCHED){ nodes{ annictId title } } ing:works(state:WATCHING){ nodes{ annictId title } } } } </code></pre> <h2 id="CSP(コンテンツセキュリティポリシー)"><a href="#CSP%28%E3%82%B3%E3%83%B3%E3%83%86%E3%83%B3%E3%83%84%E3%82%BB%E3%82%AD%E3%83%A5%E3%83%AA%E3%83%86%E3%82%A3%E3%83%9D%E3%83%AA%E3%82%B7%E3%83%BC%29">CSP(コンテンツセキュリティポリシー)</a></h2> <p>アニメのOGPがない場合は公式twitterアカウントの画像を使用しているが、CSPなどで同じサイトでないコンテンツは表示できなくなったので、<br /> URLに「twitter」が含まれる場合はサーバにプロキシさせて画像を読み込むようにした。</p> <h1 id="ご意見"><a href="#%E3%81%94%E6%84%8F%E8%A6%8B">ご意見</a></h1> <p><a target="_blank" rel="nofollow noopener" href="https://ikens.net/ckoshien_tech/annict-suggest?v=1">こちら</a>から使ってみた感想・ご意見をお寄せください。</p> ckoshien tag:crieit.net,2005:PublicArticle/15862 2020-04-23T00:32:48+09:00 2020-04-23T00:32:48+09:00 https://crieit.net/posts/b54c153b71a3e506c51b61480e03c38c 自分だけのオリジナル紙面を作れる「スポーツ新聞メーカー」をリリースしました。 <h1 id="サービスURL"><a href="#%E3%82%B5%E3%83%BC%E3%83%93%E3%82%B9URL">サービスURL</a></h1> <ul> <li><a target="_blank" rel="nofollow noopener" href="https://sports-news-maker.netlify.app">https://sports-news-maker.netlify.app</a></li> </ul> <h1 id="何ができるか"><a href="#%E4%BD%95%E3%81%8C%E3%81%A7%E3%81%8D%E3%82%8B%E3%81%8B">何ができるか</a></h1> <p><a href="https://crieit.now.sh/upload_images/668008c5ca92ae17eb989010b49994385ea05f1a6faea.jpg" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/668008c5ca92ae17eb989010b49994385ea05f1a6faea.jpg?mw=700" alt="" /></a></p> <ul> <li>新聞名</li> <li>見出し</li> <li>試合のスコア(3-10イニングまで)</li> <li>画像</li> <li>縦見出し</li> <li>写真のキャプション</li> </ul> <p>を編集してこのようなオリジナル紙面を作れます。</p> <h1 id="特徴"><a href="#%E7%89%B9%E5%BE%B4">特徴</a></h1> <h2 id="入力フォームの隠蔽"><a href="#%E5%85%A5%E5%8A%9B%E3%83%95%E3%82%A9%E3%83%BC%E3%83%A0%E3%81%AE%E9%9A%A0%E8%94%BD">入力フォームの隠蔽</a></h2> <p><a href="https://crieit.net/posts/1bbf25a7cba4095f0fd1afe817b553dd">パワプロ風画面ジェネレータ</a>で作ったコンポーネントを流用しています。<br /> クリックするまで入力フォームが出ない仕様です。</p> <h2 id="縦書きの見出し"><a href="#%E7%B8%A6%E6%9B%B8%E3%81%8D%E3%81%AE%E8%A6%8B%E5%87%BA%E3%81%97">縦書きの見出し</a></h2> <p>縦書きをCSSでデザインしています。<br /> writing-modeというプロパティだけでは、英文字が横になってしまうので、text-orientationというプロパティで英文字対応を行っています。</p> <pre><code class="javascript">style=<span>{</span><span>{</span> writingMode:'vertical-rl', textOrientation:'upright', fontSize:`calc(6vw - ${store.getState().newsTitle.length/2}px )`, margin:5, width:'1em', whiteSpace:'nowrap', fontWeight:'bold' <span>}</span><span>}</span> </code></pre> <h2 id="レスポンシブ"><a href="#%E3%83%AC%E3%82%B9%E3%83%9D%E3%83%B3%E3%82%B7%E3%83%96">レスポンシブ</a></h2> <p>フォントサイズや画像の大きさ、マージンなどを画面の幅によって変わるようCSSで実装しています。</p> <h2 id="可変イニング数"><a href="#%E5%8F%AF%E5%A4%89%E3%82%A4%E3%83%8B%E3%83%B3%E3%82%B0%E6%95%B0">可変イニング数</a></h2> <p>3~10回までイニング数を変えることができるようになっています。</p> <h2 id="セピアフィルター"><a href="#%E3%82%BB%E3%83%94%E3%82%A2%E3%83%95%E3%82%A3%E3%83%AB%E3%82%BF%E3%83%BC">セピアフィルター</a></h2> <p>画像に新聞らしくセピアフィルターをかけています。<br /> へぇ、CSSってこんなこともできるんだ!という新しい発見がありますね。</p> <pre><code class="html"><img style=<span>{</span><span>{</span> maxWidth:'85vw', objectFit:'cover', filter:'sepia(30%)', <span>}</span><span>}</span> src={this.state.image_src}/> </code></pre> <h1 id="振り返り"><a href="#%E6%8C%AF%E3%82%8A%E8%BF%94%E3%82%8A">振り返り</a></h1> <p>今回はほぼ一日で作ったので、またフロントで完結しています。<br /> 時間があればOGPでシェア機能つけたら面白いかもしれません。<br /> あとは試合スコアのチーム名を長くすると表示が乱れるのは課題です。<br /> 幅を固定してフォントサイズを文字長で反比例させればいいですかね。</p> ckoshien tag:crieit.net,2005:PublicArticle/15776 2020-03-22T17:10:52+09:00 2020-03-22T20:38:57+09:00 https://crieit.net/posts/web1week 敗者のweb1week <p>先週行われた <a href="https://crieit.net/boards/web1week-202003">web1week</a>。<br /> 初開催にも関わらず50を超えるサービスが登場し、大盛況のうちに幕を閉じた。</p> <p>経験者ならば理解を得られると思うが、個人開発という舞台には魔物が棲んでいる。<br /> 着手時点で思い描いた予定などペロリと喰われてしまう。</p> <p>不意に削られる作業時間。<br /> 理想と妥協の狭間で揺れる要件。<br /> 技術仕様の落とし穴。<br /> サービスの意義を自問自答し続ける日々。<br /> リリース直後に訪れる無慈悲な障害。</p> <p>これらを乗り越えローンチされた個人サービスの奥には、語られることのない物語が潜んでいる。<br /> そんなサービスが50以上も並ぶweb1weekのボードは圧巻で、たとえHello Worldの様なサービスでも私の心を踊らせてくれる。<br /> とても甲乙など付けられない。</p> <p>しかし「敗者」は存在する。<br /> 「web1week楽しみ!」と方方で意気込みを語り、「やってみませんか?」と様々な人を勧誘した挙句、あろうことか最小限の機能でさえ1weekに間に合わず、最終的には要件すら満たせなかった。<br /> 紛うことなき「敗者」だ。<br /> 個人開発界隈ではよく「リリースすることが大事」と言われている。その通りだ。リリースさえすれば勝ちなのだ。</p> <p>これは、そんな勝負に敗北を喫した男の記録である。</p> <h1 id="立案"><a href="#%E7%AB%8B%E6%A1%88">立案</a></h1> <p>月曜の午前0時、お題「Home」が発表された。<br /> こじつけでも何でも「Home」を絡ませたWebサービスならクリアとのこと。<br /> 初学者でも参加し易いうえに、CGMやIoTを絡めたサービスも構想でき、かつ疫病の脅威に怯える時勢に寄り添った素晴らしいお題だと思った。<br /> それと同時に頭を抱えた。<br /> 1週間で作り上げるとなると出来る事は限られている。ピンと来るアイディアが何も思い浮かばない。</p> <p>「自分が実装してみたい機能」というアプローチで考えてみる。<br /> 我が家には<a target="_blank" rel="nofollow noopener" href="https://store.google.com/jp/product/google_nest_hub">Google Nest Hub</a>というガジェットが存在する。ディスプレイ付きスマートスピーカーというイロモノだ。<br /> スマート家電のような贅沢品は存在しない我が家だが、昨年の私の誕生日に妻から贈ってもらっていた。しかし、動画を見るにはタブレットやテレビに転送で事足りるし、BGMをかける風習も無い。なかなか使う場面が訪れなかった。<br /> これを何とか活用できないか。</p> <p>玄関や冷蔵庫に貼ってるホワイトボード。これをNest Hubで代用できないかと考えた。<br /> 「いってらっしゃい」「おやつにわらび餅を冷蔵庫に入れてます」の様な、家族間のメッセージは手書きの方が嬉しかったりする。<br /> しかし、ホワイトボードのデメリットはホワイトボードの場所へ行かないと書けないことだ。<br /> 仕事中に「冷蔵庫にわらび餅入れてるの忘れてた!」となった場合、LINEに頼るしかない。<br /> スマホから手書きでメッセージを書き、玄関やリビングに置かれたNest Hubに表示されたらエモいのでは。</p> <p>Nest Hubで表示するWebアプリについて調べてみる。<br /> どうやら<a target="_blank" rel="nofollow noopener" href="https://developers.google.com/assistant/interactivecanvas">Interactive Canvas</a>というフレームワークで実装するらしい。<br /> しかし、基本的にVUI(音声ユーザーインターフェース)が前提となる作りで、ストアへ並べるにはGoogleの承認も必要となる。<br /> 今回の要件・納期では厳しい、、</p> <p>方針を変える。<br /> Nest HubにはGoogle Photosにあるアルバムを選択して、フォトフレームとして表示する機能がある。<br /> Nest Hub側でメッセージ画像を入れたアルバムを選択して、Webサービス側ではそのメッセージ画像を更新すれば良いのはないだろうか。<br /> 調べてみた。さすがGoogle様。ちゃんと<a target="_blank" rel="nofollow noopener" href="https://developers.google.com/photos">Google PhotosのAPI</a>も存在する。</p> <p>これでいこう。</p> <ol> <li>WebサービスにGoogleアカウントでログイン</li> <li>手書きUIのCanvasでメッセージを書く。</li> <li>Nest Hub表示用のアルバムをGoogle Photosに作成</li> <li>Canvasを画像化して3.のアルバムにアップロード</li> <li>既にメッセージ画像が存在する場合は削除(アルバム内の画像は1枚のみとする)</li> <li>Nest Hubで表示</li> </ol> <p>OAuth2認証をクライアントで完結できれば、DBすら不要。<br /> この程度なら1週間でいける。</p> <p>市場調査もしてみた。</p> <blockquote class="twitter-tweet"><p lang="ja" dir="ltr">スマートディスプレイ(ディスプレイ付きスマートスピーカー)がどれぐらい普及してるのか気になるのでアンケお願いします!持ってるか持ってないか。押し入れで眠っててもOKです!</p>— きんみ | ツイッター大喜利サイト🎍ついぎり🎍作りました🙄 (@_kinmi) <a target="_blank" rel="nofollow noopener" href="https://twitter.com/_kinmi/status/1236827457027592192?ref_src=twsrc%5Etfw">March 9, 2020</a></blockquote> <p>うん、Nest Hub専用サービスは良い感じのスキマ産業のようだ(強がり)</p> <p>Nuxt.jsでプロジェクトを作成して今日の作業は終了。<br /> この時点で月曜の夜。順調だ。</p> <h1 id="裏の目的"><a href="#%E8%A3%8F%E3%81%AE%E7%9B%AE%E7%9A%84">裏の目的</a></h1> <p>常々思っていたのが「もっと手軽に画像アップロード機能を作れないか」という事だった。<br /> 画像アップロードというのは、ストレージを用意して、アップされる画像の最適化処理を組み、著作権の侵害等を考慮して規約や同意UIの設計をしなければならない。<br /> リリース後は不適切な画像が上がってないかウォッチする義務が発生するし、ストレージの残容量も気にしなければならない。<br /> 更に、増え続ける画像の管理方法(一定期間で削除するか、お金で解決するか)も検討しなければならない。<br /> 個人開発において、割とコストが高めの機能だ。</p> <p>この課題はGoogle Photos等のユーザーが保有するクラウドストレージに公開状態で保存すれば解決するのではないかと考えた。<br /> このご時世、Googleアカウントは誰でも持っている。アカウント登録時に自動で付与されるGoogle Photosの容量は高画質モードなら無制限だし、あくまで「画像を公開しているのはユーザー」という体裁を繕えるので著作権問題もグレーゾーンとなる(漫画村方式)<br /> サービス側で保有するのは画像の静的URLのみだ。</p> <p>今回、Google PhotosのAPIを使いこなせる様になれば今後の個人開発に幅を持たせることが出来るかもしれない。</p> <h1 id="ハマる"><a href="#%E3%83%8F%E3%83%9E%E3%82%8B">ハマる</a></h1> <p>コロナ自粛の影響により勤務時間が削減され、個人開発の時間は比較的多めに取れた。<br /> それでも間に合わなかったのは全てにハマったからだ。<br /> 全てだ。<br /> 時系列でハマりポイントを紹介していく。</p> <h2 id="JS ClientSDKが使いづらい"><a href="#JS+ClientSDK%E3%81%8C%E4%BD%BF%E3%81%84%E3%81%A5%E3%82%89%E3%81%84">JS ClientSDKが使いづらい</a></h2> <p>Google APIをWebで利用する場合、<a target="_blank" rel="nofollow noopener" href="https://github.com/google/google-api-javascript-client">Google API Client Library for JavaScript</a>(GAPI)というライブラリを利用する。<br /> Initial Commitは2011年。それなりに年季の入ったライブラリだ。</p> <h3 id="NPMには登録されていない。"><a href="#NPM%E3%81%AB%E3%81%AF%E7%99%BB%E9%8C%B2%E3%81%95%E3%82%8C%E3%81%A6%E3%81%84%E3%81%AA%E3%81%84%E3%80%82">NPMには登録されていない。</a></h3> <p>scriptタグで読み込む必要がある。<br /> Nuxt.jsで外部スクリプトを使用する場合、<code>$nextTicks()</code>を用いてもスクリプトのロードが完了していない場合がある。<br /> ロード状態を監視する必要があるので少し手間だ。</p> <h3 id="DiscoveryDocs"><a href="#DiscoveryDocs">DiscoveryDocs</a></h3> <p>GAPIのスクリプトを読み込んだ時点では<code>init()</code>等、最低限の機能しか保有していない。<br /> <a target="_blank" rel="nofollow noopener" href="https://github.com/google/google-api-javascript-client/blob/master/docs/start.md">Getting Started</a>に記載されているが、API Key等を設定して初期化する際に<code>DiscoveryDocs</code>というURLを設定する。<br /> DiscoveryDocsはAPI単位で用意されており、URLの先にあるJSONをGAPIでロードすれば機能として利用できる。<br /> Google Photos APIのDiscoveryDocsは下記となる。<br /> <a target="_blank" rel="nofollow noopener" href="https://content.googleapis.com/discovery/v1/apis/photoslibrary/v1/rest"><code>https://content.googleapis.com/discovery/v1/apis/photoslibrary/v1/rest</code></a><br /> スクリプトの肥大化を防ぐための機構だろう。<br /> ここまではまだ良い。手間を感じるがまだ許せる。</p> <h3 id="同期処理"><a href="#%E5%90%8C%E6%9C%9F%E5%87%A6%E7%90%86">同期処理</a></h3> <p>ドキュメントのサンプルはthenチェーンで記載されている。<br /> じゃあ・・・と思って<code>async / await</code>で書き直してみる。動かない。<br /> そう、返却値は<strong>ES6 Promiseじゃない</strong>。<br /> <a target="_blank" rel="nofollow noopener" href="https://google.github.io/closure-library/api/goog.Thenable.html">goog.Thenable</a>という独自インターフェースを継承したオブジェクトだ。<br /> <code>init().then(()=>{ })</code> の中でしかGAPIは動かない。同期的にいくつものAPIを使いたければthenのネストを深め続けるしかない。F*ck。</p> <p>ES6が標準化されたのが2015年。このライブラリの開発当初ならスマートな仕様だったのだろう。<br /> しかし、こちとら去年からJSを学び始めた身。<br /> 終始、この仕様に慣れず生産性が低下した。</p> <h2 id="認証で躓く"><a href="#%E8%AA%8D%E8%A8%BC%E3%81%A7%E8%BA%93%E3%81%8F">認証で躓く</a></h2> <p>ユーザー管理には<a target="_blank" rel="nofollow noopener" href="https://firebase.google.com/docs/auth?hl=ja">Firebase Authentication</a>を採用した。<br /> 手軽に導入でき、「特定のユーザーには使わせない」といったセキュリティ対策も出来る。<br /> Twitter API等、他社のAPIと連携する場合はトークンの保管といった一手間が必要になってくるが、今回はGoogle Platform内で連携するだけ。<br /> 簡単だろうと思った。</p> <h3 id="🙅 Firebase → Google API"><a href="#%F0%9F%99%85+Firebase+%E2%86%92+Google+API">🙅 Firebase → Google API</a></h3> <p>以下の記事を参考に、Firebase UIでログイン→GAPIのイニシャライズを試みる。<br /> <a target="_blank" rel="nofollow noopener" href="https://medium.com/google-cloud/using-google-apis-with-firebase-auth-and-firebase-ui-on-the-web-46e6189cf571">Using Google APIs with Firebase Auth and Firebase UI on the Web</a></p> <p><strong>できない。</strong><br /> この記事ではFirebaseでログインした時点で <em>GAPIの認証状態</em> : <code>gapi.auth2.getAuthInstance().isSignedIn.get()</code>が <code>true</code> になる想定だが <code>false</code> だ。<br /> GAPIでもログインする必要があるのか?と思って試したがFirebaseとGAPIで二重ログインされてしまった。</p> <p>記事のコメント欄に<a target="_blank" rel="nofollow noopener" href="https://medium.com/@slashdrew/this-appears-to-be-no-longer-functional-cc2ca49db640">記載がされている</a>が、現在はFirebaseでログインしてもGAPIでログイン状態を検知しない。<br /> 下記のissueにもその事が記載されており、ステータスはopenのままだ。<br /> <a target="_blank" rel="nofollow noopener" href="https://github.com/google/google-api-javascript-client/issues/561">https://github.com/google/google-api-javascript-client/issues/561</a></p> <h3 id="🙆 Google API → Firebase"><a href="#%F0%9F%99%86+Google+API+%E2%86%92+Firebase">🙆 Google API → Firebase</a></h3> <p>つまり現状、FirebaseとGAPIを連携するには認証順を逆にしないといけない。<br /> <em>GoogleAPIのサインイン</em> : <code>gapi.auth2.signIn()</code>でログインした後、認証情報を取得して <em>Firebaseの認証情報を使用したサインイン</em> : <code>firebase.auth().signInWithCredential()</code>でもログインするフローとなる。<br /> 下記の記事を参考に実装した。<br /> <a target="_blank" rel="nofollow noopener" href="https://fireship.io/snippets/how-to-use-google-apis-or-gapi-with-firebase-auth/">How to Use Google APIs on the Web</a></p> <h3 id="進捗:だめです"><a href="#%E9%80%B2%E6%8D%97%EF%BC%9A%E3%81%A0%E3%82%81%E3%81%A7%E3%81%99">進捗:だめです</a></h3> <p>この時点で既に平日は終わっていた。残すところは土日のみ。<br /> 対応が長期化した要因は「出来ると書いてあったから」。<br /> Firebase→GAPIの認証は不可能という可能性を考慮していなかった。<br /> この考慮漏れが発生するとプログラマーの思考はどうなるか。<br /> <strong>ひたすらタイポを探し続ける</strong>のである。</p> <h2 id="画像アップロードが用意されてない"><a href="#%E7%94%BB%E5%83%8F%E3%82%A2%E3%83%83%E3%83%97%E3%83%AD%E3%83%BC%E3%83%89%E3%81%8C%E7%94%A8%E6%84%8F%E3%81%95%E3%82%8C%E3%81%A6%E3%81%AA%E3%81%84">画像アップロードが用意されてない</a></h2> <p>まだ間に合う。<br /> 土曜にアップロード機能を実装。<br /> 日曜にデザインを微修正&実機で動作確認。<br /> ここまで盛大に躓いて尚、根拠なく「出来る」と盲信する。<br /> 進捗が遅れてるプログラマーにありがちな<strong>逆算スケジュール</strong>である。</p> <h3 id="🙅 GAPIで画像アップロード"><a href="#%F0%9F%99%85+GAPI%E3%81%A7%E7%94%BB%E5%83%8F%E3%82%A2%E3%83%83%E3%83%97%E3%83%AD%E3%83%BC%E3%83%89">🙅 GAPIで画像アップロード</a></h3> <p>ガイドラインを読んでみる。<br /> <a target="_blank" rel="nofollow noopener" href="https://developers.google.com/photos/library/guides/upload-media">https://developers.google.com/photos/library/guides/upload-media</a><br /> どうやら画像をアップロードするには、二段階の手順を踏まないといけないようだ。</p> <ol> <li>画像のバイナリデータをGoogleにアップロードする</li> <li>上記返却値に含まれるアップロードトークンを用いてアルバムに追加する。</li> </ol> <p>Google Photosの<a target="_blank" rel="nofollow noopener" href="https://content.googleapis.com/discovery/v1/apis/photoslibrary/v1/rest">DiscoveryDocs</a>から該当のメソッドを探してみる。<br /> <strong>無い。</strong><br /> 何度検索しても「uploads」という処理は無い。<br /> 「なぜ無いのか」なんて考える余裕も無い。<br /> (現在、不安になって再度探してみたがやっぱり無い)</p> <p>GAPIじゃGoogleに画像をアップロード出来ない・・・?</p> <h3 id="🙅 axiosでリクエスト"><a href="#%F0%9F%99%85+axios%E3%81%A7%E3%83%AA%E3%82%AF%E3%82%A8%E3%82%B9%E3%83%88">🙅 axiosでリクエスト</a></h3> <p>悩んでいる暇は無い。もうクライアントSDKは捨てる。<br /> ドキュメントにはエンドポイントが記載されているので、そこに対して<code>axios</code>でPOSTリクエストを投げてみる。</p> <p><strong>Network Error.</strong></p> <p>・・・久し振りに見たぜ。<br /> <code>has been blocked by CORS policy: No 'Access-Control-Allow-Origin'</code><br /> <a target="_blank" rel="nofollow noopener" href="https://developer.mozilla.org/ja/docs/Web/HTTP/CORS/Errors">CORSエラー</a>だ。<br /> おいおい、嘘だろ。CORSだと?<br /> 浅学だが、基本的にはAPI側での対応が必須だったと記憶している。詰んだか?</p> <h3 id="🙅 XMLHttpRequestでリクエスト"><a href="#%F0%9F%99%85+XMLHttpRequest%E3%81%A7%E3%83%AA%E3%82%AF%E3%82%A8%E3%82%B9%E3%83%88">🙅 XMLHttpRequestでリクエスト</a></h3> <p>ドキュメントには「CORSもサポートしているよ」と書かれている。<br /> <a target="_blank" rel="nofollow noopener" href="https://github.com/google/google-api-javascript-client/blob/master/docs/cors.md">How to use CORS to access Google APIs</a><br /> クライアントSDKを用いれば回避できるよ。と。<br /> <strong>使えねぇんだよチクショウが。</strong><br /> 記事の最後に<a target="_blank" rel="nofollow noopener" href="https://developer.mozilla.org/ja/docs/Web/API/XMLHttpRequest"><code>XMLHttpRequest</code></a>を用いた例も記載されている。<br /> 存在は知っていたが初めて使うなこれ。<br /> 愚直に書いてある通り組んでみる。</p> <p><code>has been blocked by CORS policy: No 'Access-Control-Allow-Origin'</code></p> <p>泣く。</p> <h3 id="🙅 gapi.client.request()"><a href="#%F0%9F%99%85+gapi.client.request%28%29">🙅 gapi.client.request()</a></h3> <p>まだだ。諦めない。<br /> 俺は長男だから我慢できたけど次男だったら我慢できなかった。</p> <p>ドキュメントを漁り、もう1つの可能性を見つける。<br /> クライアントSDKには標準で <em>APIを叩く為のメソッド</em> : <a target="_blank" rel="nofollow noopener" href="https://github.com/google/google-api-javascript-client/blob/master/docs/reference.md#api-requests">gapi.client.request()</a>が備わっている。<br /> GoogleAPIのエンドポイントをベタ書きして引数に渡せば叩けるらしい。<br /> クライアントSDKならCORSを回避できるんだろ?信じるぞ?<br /> 叩いてみる。</p> <p><strong>404</strong></p> <p>???<br /> タイポ探しフェーズ(数時間)に入るも、やっぱり問題は無い。<br /> リクエスト内容を確認した。POST先のURLが変わっている。<br /> 画像アップロードのエンドポイントは<code>https://photoslibrary.googleapis.com/v1/uploads</code>だが、ドメイン部が<code>content.googleapis.com</code>に変わってる。<br /> なんだこれは。<br /> 恐らく、クライアントSDKが書き換えていると推測。M*ther F*cker。</p> <p>諦めた。</p> <p>クライアントで完結することを。</p> <h3 id="🙆 Netlify Functions"><a href="#%F0%9F%99%86+Netlify+Functions">🙆 Netlify Functions</a></h3> <p>ホスト先にNetlifyを利用している。<br /> そこでクラウド関数を作れるNetlify Functionsを使ってみようと思い立った。<br /> 使った経験が無い機能だ。下記の記事を参考にHello Worldから始めてみる。<br /> <a target="_blank" rel="nofollow noopener" href="https://qiita.com/Sr_Bangs/items/7867853f5e71bd4ada56">【入門】Netlify Functionsコトハジメ</a><br /> そして「Googleに画像をアップロードしてアップロードトークン返すクラウド関数」を立ててみた。<br /> うまくいった。<br /> 初めて使う機能なのに、ハマらず動く。ローカル環境もすぐ出来た。<br /> 感動した。モダン最高。</p> <h3 id="進捗:納期は昨日なので大丈夫です"><a href="#%E9%80%B2%E6%8D%97%EF%BC%9A%E7%B4%8D%E6%9C%9F%E3%81%AF%E6%98%A8%E6%97%A5%E3%81%AA%E3%81%AE%E3%81%A7%E5%A4%A7%E4%B8%88%E5%A4%AB%E3%81%A7%E3%81%99">進捗:納期は昨日なので大丈夫です</a></h3> <p>この時点で日曜の夜。<br /> 軸となる機能は一通り完成したものの、「同名アルバムが存在していた場合」などのシチュエーションに応じた分岐は作れてないし、要件として「既にメッセージ画像が存在していた場合は更新する」といった機能も存在するが未実装だ。実機確認もやれてない。</p> <p>「ごめんなさい」しつつ、月曜に出そう。<br /> そう決めて床についた。</p> <h2 id="画像の削除/更新はできない"><a href="#%E7%94%BB%E5%83%8F%E3%81%AE%E5%89%8A%E9%99%A4%2F%E6%9B%B4%E6%96%B0%E3%81%AF%E3%81%A7%E3%81%8D%E3%81%AA%E3%81%84">画像の削除/更新はできない</a></h2> <p>ベットに入った直後、悪寒がした。</p> <p><strong>見覚えがない。</strong></p> <p>散々GoogleAPIのドキュメントと格闘し続けたが、Google Photos内の写真を<strong>削除/更新するエンドポイントは見た記憶がない</strong>。<br /> スマホを取り出し確認してみるも、やはり無い。</p> <p>冗談だろ?クラウドストレージを操作するAPIのくせに削除/更新は出来ない?そんな馬鹿な話があるか。<br /> 調べてみる。<br /> <a target="_blank" rel="nofollow noopener" href="https://issuetracker.google.com/issues/109759781">https://issuetracker.google.com/issues/109759781</a><br /> 結論:<strong>出来ないらしい</strong></p> <p>詰みです。お疲れ様でした。</p> <p>過去画像を含めてランダム表示されるなんてホワイトボードじゃない。</p> <p>「お手数ですがGoogle Photosを開いて、ご自身で過去の画像を削除してください。」<br /> しょーもな。<br /> サービスとして成立していない。</p> <p>・・・いや、まだだ。</p> <p>よし、譲ろう。</p> <p>百歩譲ろう。</p> <p>Google Driveなら削除/更新もAPIで可能じゃなかろうか。<br /> Photosなんかより歴史あるクラウドストレージだ。<br /> Google Driveへアップした写真をNest Hubで表示できれば問題無い。</p> <p><a target="_blank" rel="nofollow noopener" href="https://support.google.com/googlenest/thread/671055">Can I connect my Google Drive to my Google home hub? </a><br /> 結論:<strong>出来ないらしい</strong></p> <p>こうして私は、web1weekの敗者となった。</p> <h2 id="Safari対応(おまけ)"><a href="#Safari%E5%AF%BE%E5%BF%9C%EF%BC%88%E3%81%8A%E3%81%BE%E3%81%91%EF%BC%89">Safari対応(おまけ)</a></h2> <p>負け犬でも遠吠えぐらいは出来る。<br /> 「手書き画像をGoogle Photosへアップするサービス」としてローンチしよう。<br /> そう決めて実機確認に入った。</p> <h3 id="CORSぞ"><a href="#CORS%E3%81%9E">CORSぞ</a></h3> <p>MacのChrome、Android Chromeは問題なく動作完了。<br /> 妻に「iPhoneでこれ使えるか確認してほしい」と依頼する。<br /> 返信は「画面が驚きの白さ」。<br /> ここで初めてMacのSafariでも動作確認してみる。</p> <p><code>has been blocked by CORS policy: No 'Access-Control-Allow-Origin'</code></p> <p><strong>CORSぞ</strong>。</p> <p>何度立ちはだかれば気が済むのだCORSよ。<br /> まぁ思い当たる節はあった。<br /> GAPIが使い辛いので、下記のようにクラアントSDKを動的に読み込んでplugin化していたのだ。</p> <pre><code class="javascript">export default async ({ $axios, store }, inject) => { // GAPIの読み込み const gapiScript = document.createElement('script') const src = await $axios.$get('https://apis.google.com/js/api.js') gapiScript.appendChild(document.createTextNode(src)) document.head.appendChild(gapiScript) const gapi = window.gapi // ロード処理(Promise化) const clientLoad = new Promise((resolve, reject) => { gapi.load('client:auth2', () => { resolve() }) }) // GAPI初期化処理 const init = () => { return clientLoad.then(() => { return gapi.client.init({ apiKey: authConfig.Google.apiKey, clientId: authConfig.Google.clientId, discoveryDocs: authConfig.Google.discoveryDocs, scope: authConfig.Google.scopes.join(' ') }) }) } inject('gapi', gapi) inject('gapiInit', init) } </code></pre> <p>不安はあったが、動いたのでそのままにしていた。<br /> 外部スクリプトを動的に読み込む場合、普通はCORSの問題が発生する。<br /> 素直にscriptタグで読み込むように変更した。</p> <p>というか、なぜChromeでは読み込めたのか分からない。<br /> 同じGoogle製品だから?<br /> プラットフォーマー恐るべし。</p> <h3 id="クロスサイトトラッキング"><a href="#%E3%82%AF%E3%83%AD%E3%82%B9%E3%82%B5%E3%82%A4%E3%83%88%E3%83%88%E3%83%A9%E3%83%83%E3%82%AD%E3%83%B3%E3%82%B0">クロスサイトトラッキング</a></h3> <p>さて、初期表示までは問題なくSafariで動作した。<br /> しかしGoogleへログインしても認証状態を検知しない。<br /> プログラムが「ログイン中」と判断しないのだ。<br /> こればっかりは思い当たる節も無い。</p> <p>issueが上がっていた。<br /> <a target="_blank" rel="nofollow noopener" href="https://github.com/google/google-api-javascript-client/issues/503">gapi auth2 issue on safari</a><br /> 同様の事象、かつオープンのままだ。<br /> Safariの場合、Cookieを全削除するか、プライベートブラウズなら問題無いらしい。<br /> マジかよ・・・<br /> 頑張れば何か対策できるのかもしれないが、負け犬にそんな根性は無い。<br /> UserAgentから「Safari、もしくはiPhone/iPad」を判定して、該当する場合は長ったらしい注意文言を表示するようにした。</p> <h1 id="敗因"><a href="#%E6%95%97%E5%9B%A0">敗因</a></h1> <p>次回以降のweb1weekで勝つための敗因分析を行う。</p> <h2 id="PoC(概念実証)不足"><a href="#PoC%EF%BC%88%E6%A6%82%E5%BF%B5%E5%AE%9F%E8%A8%BC%EF%BC%89%E4%B8%8D%E8%B6%B3">PoC(概念実証)不足</a></h2> <p>一番の原因はこれ。<br /> 事前に「画像の削除/更新は出来ない」と知っていれば別案を採用していた。<br /> とはいえ、1週間という過密スケジュールで検証工程を取れるかは疑問が残る。<br /> 少なくとも、</p> <ol> <li>機能の洗い出し</li> <li>実装に必要な外部インターフェースのドキュメントはちゃんと読む</li> </ol> <p>まぁ、、当たり前のことはちゃんとしよう。ということ。</p> <h2 id="「出来ない」の判断が遅い"><a href="#%E3%80%8C%E5%87%BA%E6%9D%A5%E3%81%AA%E3%81%84%E3%80%8D%E3%81%AE%E5%88%A4%E6%96%AD%E3%81%8C%E9%81%85%E3%81%84">「出来ない」の判断が遅い</a></h2> <p>認証や画像アップロード等、ハマった際に「出来ない」という判断が出来ていない。<br /> 昨今の潤沢な開発環境に甘え、「出来ない事は無い」と思い込んでいる節がある。<br /> 確かに大抵の場合、ハマる原因は外部インターフェースの仕様変更、もしくはタイポが多い。<br /> 塩梅が難しいが、もっと早いタイミングでissueを探そう。</p> <h1 id="Google Photosを利用した画像アップローダーは作れるのか"><a href="#Google+Photos%E3%82%92%E5%88%A9%E7%94%A8%E3%81%97%E3%81%9F%E7%94%BB%E5%83%8F%E3%82%A2%E3%83%83%E3%83%97%E3%83%AD%E3%83%BC%E3%83%80%E3%83%BC%E3%81%AF%E4%BD%9C%E3%82%8C%E3%82%8B%E3%81%AE%E3%81%8B">Google Photosを利用した画像アップローダーは作れるのか</a></h1> <p>冒頭に記した「裏の目的」について。<br /> そもそもGoogle Photos APIの<a target="_blank" rel="nofollow noopener" href="https://developers.google.com/photos/library/guides/acceptable-use">利用規約</a>には「ホスティングサービスとして使うならCloud Storageを使え」と明記してある。<br /> 開発するサービスを「Google Photosへアップした写真の共有ギャラリー」という位置付けにするならグレーかな?と思ったが、<br /> 結論から言うと「多分、不可」だ。<br /> 少なくとも、そう言うサービスを作れるだけのAPIが提供されていない。</p> <p>まず前述の通り、API経由での画像の削除/更新は出来ない。<br /> あくまで画像のアップロードのみで、アルバム間の移動すら出来ない。</p> <p>そして、更に致命的なことに<strong>静的なURLは取得できない</strong>。<br /> 写真のURLを取得するAPIは存在するが、一定期間が過ぎると無効化されるらしい。<br /> これはGoogle Photosの「URLで共有する」と同じURLなのだろう。<br /> URL先には静的な画像が埋め込まれているらしいが、それを取得するAPIは存在しない。<br /> スクレイピングを駆使すれば取得できるかもしれないが、実用的では無いだろう。</p> <p>「ユーザーのプライベートストレージを用いた画像アップローダー」は別サービスを検討した方が良さそうだ。</p> <h1 id="ゴミを公開する"><a href="#%E3%82%B4%E3%83%9F%E3%82%92%E5%85%AC%E9%96%8B%E3%81%99%E3%82%8B">ゴミを公開する</a></h1> <p>これはweb1weekじゃない。<br /> 私が作りたかったものでもない。<br /> ゴミだ。</p> <p><a target="_blank" rel="nofollow noopener" href="https://nest-board.netlify.com/">Nest Board</a><br /> <a target="_blank" rel="nofollow noopener" href="https://nest-board.netlify.com/">https://nest-board.netlify.com/</a></p> <p>Google APIは申請無しで使用できるが、Googleから承認されていない場合、ログイン時に警告画面が出る。<br /> 利用する際は注意文言を読んで頂きたい。</p> <p>需要あるか分からないが、Nest Hub側で表示する際の手順も明記しておく。</p> <blockquote> <p><strong>1. スマホ(iPhone/Android)でGoogle Homeアプリを開く</strong><br /> Nest Hubの初期設定に必要なアプリなのでインストールされているはず。<br /> <br /> <strong>2. 表示したいNest Hub > フォトフレームを編集 > Google フォト の順で選択</strong></p> <p><strong>3. Nest Boardで作成したアルバムを選択する</strong><br /> 初期値は「Nest Board」<br /> <br /> <strong>4. 「フォトフレーム」画面の下部にある「個人的な写真の整理」を「リアルタイム共有アルバムのみ」にする</strong><br /> これを設定しないとNest Hubがフォトフレームに不向きな写真と判定して表示されない。<br /> ハマりポイント。</p> </blockquote> <p>以上の手順を踏めば、Nest Hubに手書きのメッセージ画像が表示される。</p> <p><a href="https://crieit.now.sh/upload_images/e5d58ba13a25c14fac70e13548eff8d95e770af16b1cc.jpg" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/e5d58ba13a25c14fac70e13548eff8d95e770af16b1cc.jpg?mw=700" alt="敗北者じゃけぇ" /></a></p> <p>普段はソースコードを公開したりしないのだが、今回はリポジトリをpublicにした。<br /> なぜなら敗者だからだ。<br /> <a target="_blank" rel="nofollow noopener" href="https://github.com/kin-mi/nest-board">https://github.com/kin-mi/nest-board</a></p> <h1 id="おわり"><a href="#%E3%81%8A%E3%82%8F%E3%82%8A">おわり</a></h1> <p>次は勝つ。</p> きんみ tag:crieit.net,2005:PublicArticle/15775 2020-03-20T13:51:56+09:00 2020-03-22T06:54:01+09:00 https://crieit.net/posts/Nuxt-5-Firebase-GAE-Netlify-Heroku Nuxtアプリを無料で公開するときに試した5つの環境まとめ(Firebase/GAE/Netlify/Heroku) <p>最近Nuxtでいろいろ作っているけど、無料で使える環境をいろいろ試してる。<br /> いろいろメリデメあるけど、SPAならNetlify/SSRならHerokuがよさそう。<br /> いままで試したものをまとめてみた。</p> <h3 id="ほしかったもの"><a href="#%E3%81%BB%E3%81%97%E3%81%8B%E3%81%A3%E3%81%9F%E3%82%82%E3%81%AE">ほしかったもの</a></h3> <p>主に開発してるのが<a target="_blank" rel="nofollow noopener" href="https://tsundoku.site">CGM系のWebサービス</a>なので、</p> <ol> <li>動的なOGP画像などが設定できる(OGP芸)</li> <li>カスタムドメインが使える</li> <li>日次のランキング集計などの定期実行ができる</li> </ol> <p>が、無料でできて、なるべく実装が楽で、そこまで遅くないのがうれしい。</p> <h3 id="試した5つのパターン"><a href="#%E8%A9%A6%E3%81%97%E3%81%9F5%E3%81%A4%E3%81%AE%E3%83%91%E3%82%BF%E3%83%BC%E3%83%B3">試した5つのパターン</a></h3> <p>試したのは以下の5パターン。試してみた順で記載。</p> <ol> <li>Nuxt(SSR) + Cloud Function<br /> 起動がかなり遅かった。。実装も大変なのでNG</li> <li>Nuxt(SPA) + Firebase Hosting<br /> 構築はかなり楽。ただ、OGP芸が大変でFunctionsが必要</li> <li>Nuxt(SPA) + Netlify<br /> プレレンダリングでOGP芸が楽。定期実行はFunctionsでできる</li> <li>Nuxt(SSR) + GAE(f1:256M)<br /> メモリの制限きつく、たまに落ちる。。定期実行はcron.yamlでできる</li> <li>Nuxt(SSR) + Heroku(free:512M) + Cloudflare<br /> メモリ多くてよい。SSLはないのでCloudflareを併用。定期実行はHeroku Scheduler</li> </ol> <p>SPAで十分であれば、「3.Nuxt(SPA) + Netlify」が構築も簡単で良かった。<br /> <a target="_blank" rel="nofollow noopener" href="https://tsundoku.site">積読ハウマッチ</a>は、現在この構成。</p> <p>SSRの場合、「5. Nuxt(SSR) + Heroku(free:512M) + CloudFlare」が良い感じ。<br /> <a target="_blank" rel="nofollow noopener" href="https://hen-ai.net/">へんあいマップ</a>がこの構成。</p> <h3 id="各パターンについて"><a href="#%E5%90%84%E3%83%91%E3%82%BF%E3%83%BC%E3%83%B3%E3%81%AB%E3%81%A4%E3%81%84%E3%81%A6">各パターンについて</a></h3> <h4 id="1. Nuxt(SSR) + Cloud Function"><a href="#1.+Nuxt%28SSR%29+%2B+Cloud+Function">1. Nuxt(SSR) + Cloud Function</a></h4> <p>一番はじめに<a target="_blank" rel="nofollow noopener" href="https://tsundoku.site">積読ハウマッチ</a>でやろうとした構成。</p> <p><img width="870" alt="スクリーンショット 2020-03-20 11.43.40.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/478782/7fa5a956-cec1-9117-1d5d-f769eae53a32.png"></p> <p>細かいやり方は、以下の記事の「Firebase Cloud Functionsの設定してSSRできるようにする」あたりに。<br /> ・<a target="_blank" rel="nofollow noopener" href="https://www.memory-lovers.blog/entry/2019/04/02/115149">Nuxt.jsではじめるときのやることリスト(SSRも国際化も自動デプロイも) - くらげになりたい。</a></p> <p>当時は動的ページのSEO対応するには、この方法が多く検索に出ていたので試したけど、<br /> Cloud Functionのコールドスタートがつらすぎてお蔵入り。。SPAでいくことに。。</p> <h4 id="2. Nuxt(SPA) + Firebase Hosting"><a href="#2.+Nuxt%28SPA%29+%2B+Firebase+Hosting">2. Nuxt(SPA) + Firebase Hosting</a></h4> <p><a target="_blank" rel="nofollow noopener" href="https://tsundoku.site">積読ハウマッチ</a>をリリースしたときの構成。</p> <p><img width="910" alt="スクリーンショット 2020-03-20 11.37.12.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/478782/37459683-872a-5047-a383-71c33f0c640e.png"></p> <p>課題だった動的ページのSEOは、Cloud Functionsを経由して返す方法で対応。<br /> こちらも当時よく検索に出てたけど、実装が大変そうだったので2番手に。</p> <p>細かいやり方は、以下の記事に。(2020/03/22追記: OGP画像生成系の記事を追加)<br /> ・<a target="_blank" rel="nofollow noopener" href="https://www.memory-lovers.blog/entry/2019/08/07/150000">Nuxt(SPA)+FirebaseでSEO!OGP!: 特定のパスだけheadだけ返すやつ - くらげになりたい。</a><br /> ・<a target="_blank" rel="nofollow noopener" href="https://qiita.com/kira_puka/items/8c1d1240c3aa200cbec0">Nuxt.js(SPA)+Firebaseで積読用の読書管理サービスを作ってみたときにハマったこと - Qiita</a><br /> ・<a target="_blank" rel="nofollow noopener" href="https://www.memory-lovers.blog/entry/2019/06/26/194500">Cloud Functions + ImageMagickでOPG画像の動的生成してCloud Storageにアップロードする - くらげになりたい。</a><br /> ・<a target="_blank" rel="nofollow noopener" href="https://www.memory-lovers.blog/entry/2020/02/19/150000">Cloud FunctionとSVGでOGP画像生成を試行錯誤したまとめ - くらげになりたい。</a></p> <p>カスタムドメインについては、Firebase HostingでSSLと合わせて無料で設定可能。</p> <p>定期実行は、Cloud Functionsでできるのでそれを利用した。<br /> ・<a target="_blank" rel="nofollow noopener" href="https://firebase.google.com/docs/functions/schedule-functions?hl=ja">関数のスケジュール設定  |  Firebase</a><br /> ・<a target="_blank" rel="nofollow noopener" href="https://qiita.com/kira_puka/items/1f164dd8d1a5a281d9c1">Cloud Functions for Firebaseのcronみたいな定期実行を試したら簡単だった - Qiita</a></p> <p>ただ、Cloud Functionsの定期実行は、<br /> 「<strong>Googleアカウントごとに3つのジョブ</strong>を無料で使用できる」<br /> なので、注意が必要。<strong>プロジェクトごとじゃない</strong>。。</p> <h4 id="3. Nuxt(SPA) + Netlify"><a href="#3.+Nuxt%28SPA%29+%2B+Netlify">3. Nuxt(SPA) + Netlify</a></h4> <p><a target="_blank" rel="nofollow noopener" href="https://tsundoku.site">積読ハウマッチ</a>の現在の構成。</p> <p><img width="775" alt="スクリーンショット 2020-03-20 11.47.02.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/478782/5010c13a-98a9-c9e0-ae3b-767d18cd8006.png"></p> <p><a target="_blank" rel="nofollow noopener" href="https://docs.netlify.com/site-deploys/post-processing/prerendering/#set-up-prerendering">NetlifyのPrerendering</a>が無料化されて使えるようになり、よいという話を聞くように。。</p> <p>実装が複雑で、変更もしにくかったので、この方法に。<br /> Netlify自体がCDNも提供していているので、すこしはやくなった(気がする)</p> <p>カスタムドメインも、NetlifyのDNSを設定して無料で対応できる。</p> <p>定期実行は、2.と同じ感じで、Cloud Functionsのまま。</p> <p>コード量も減って変更もしやすくなったので、OGP画像の改善とかが楽にできるように。。(<em>´ω`</em>)</p> <h4 id="4. Nuxt(SSR) + GAE(f1:256M)"><a href="#4.+Nuxt%28SSR%29+%2B+GAE%28f1%3A256M%29">4. Nuxt(SSR) + GAE(f1:256M)</a></h4> <p>SPAの課題として、初期表示が遅いのでなんとかしたいなと、SSRの環境を模索しはじめ。。<br /> 公開していないけど、1つ作ってみた。</p> <p><img width="798" alt="スクリーンショット 2020-03-20 13.17.06.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/478782/c24ce0b2-64b3-1ea4-9bd2-e2150ba13d14.png"></p> <p>試してみたところ、起動するだけでメモリ上限すれすれで、複数アクセスがあったりすると、落ちる場合も。。<br /> (ts-nodeで動かしているのも悪い気がしている。。)</p> <p><img width="561" alt="スクリーンショット 2020-03-20 12.22.00.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/478782/e3940627-4141-4c17-3c82-b2ee13ca1e14.png"></p> <p>ただ、定期実行はcron.yamlを用意すればURLに送信でき、無料範囲も広い。<br /> 料金も「<a target="_blank" rel="nofollow noopener" href="https://cloud.google.com/appengine/pricing?hl=ja#%E3%81%9D%E3%81%AE%E4%BB%96%E3%81%AE%E3%83%AA%E3%82%BD%E3%83%BC%E3%82%B9">その他のリソース</a>」をみるとcronは無料のよう。<br /> ・<a target="_blank" rel="nofollow noopener" href="https://cloud.google.com/appengine/docs/flexible/nodejs/scheduling-jobs-with-cron-yaml?hl=ja">cron.yaml を使用したジョブのスケジューリング</a></p> <p>カスタムドメインやSSLも無料で利用できる感じ。(試してないので未確認)</p> <h4 id="5. Nuxt(SSR) + Heroku(free:512M) + Cloudflare"><a href="#5.+Nuxt%28SSR%29+%2B+Heroku%28free%3A512M%29+%2B+Cloudflare">5. Nuxt(SSR) + Heroku(free:512M) + Cloudflare</a></h4> <p>GAEでうまくいかなかったので、無料でメモリも多いHerokuを利用。<br /> <a target="_blank" rel="nofollow noopener" href="https://hen-ai.net/">へんあいマップ</a>がこの構成で稼働中。</p> <p><img width="885" alt="スクリーンショット 2020-03-20 13.24.02.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/478782/60f2ab5c-3f4a-9b4b-ac45-341ed99c4667.png"></p> <p>定期実行は無料のアドオン(<a target="_blank" rel="nofollow noopener" href="https://elements.heroku.com/addons/scheduler">Heroku Scheduler</a>)が利用できる。<br /> Nuxt側で<a target="_blank" rel="nofollow noopener" href="https://ja.nuxtjs.org/api/configuration-servermiddleware/">serverMiddleware</a>を用意すればOK。</p> <p>ただ、メモリは多く定期実行もが、Herokuの無料枠だと制限も多い。</p> <ol> <li>SSLは非対応</li> <li>30分アクセスがないとスリープする</li> <li>クレジットカードの登録で1000時間/月xアカウント<br /> (未認証だと550時間/月xアカウント)</li> </ol> <h5 id="1. SSLは非対応"><a href="#1.+SSL%E3%81%AF%E9%9D%9E%E5%AF%BE%E5%BF%9C">1. SSLは非対応</a></h5> <p>単体だけだとSSLに対応していないので、<a target="_blank" rel="nofollow noopener" href="https://www.cloudflare.com/ja-jp/">Cloudflare</a>を併用。<br /> CloudflareがCDNも提供してくれるので良い感じに。</p> <h5 id="2. 30分アクセスがないとスリープする"><a href="#2.+30%E5%88%86%E3%82%A2%E3%82%AF%E3%82%BB%E3%82%B9%E3%81%8C%E3%81%AA%E3%81%84%E3%81%A8%E3%82%B9%E3%83%AA%E3%83%BC%E3%83%97%E3%81%99%E3%82%8B">2. 30分アクセスがないとスリープする</a></h5> <p>これは、Heroku Schedulerを使えばOK。<br /> 任意のURLを叩けるため、15分毎などスリープしないようにしておく。</p> <h5 id="3. 無料枠は1000時間/月xアカウント"><a href="#3.+%E7%84%A1%E6%96%99%E6%9E%A0%E3%81%AF1000%E6%99%82%E9%96%93%2F%E6%9C%88x%E3%82%A2%E3%82%AB%E3%82%A6%E3%83%B3%E3%83%88">3. 無料枠は1000時間/月xアカウント</a></h5> <p>1000時間あれば、24時間x31日でも744時間なので、大丈夫な感じ。<br /> ただ、アカウント単位での無料枠なので、複数アプリを無料で稼働はできない。。</p> <h5 id="実際のやりかたとかは、"><a href="#%E5%AE%9F%E9%9A%9B%E3%81%AE%E3%82%84%E3%82%8A%E3%81%8B%E3%81%9F%E3%81%A8%E3%81%8B%E3%81%AF%E3%80%81">実際のやりかたとかは、</a></h5> <p>以下の記事に。<br /> ・<a target="_blank" rel="nofollow noopener" href="https://www.memory-lovers.blog/entry/2020/01/25/120000">Heroku+CloudflareなNuxtでSSRしてみる - くらげになりたい。</a></p> <h3 id="まとめ"><a href="#%E3%81%BE%E3%81%A8%E3%82%81">まとめ</a></h3> <p>いろいろ試してみたけど、Nuxt.jsを無料で公開するときは、</p> <ul> <li>SPAならNetlify <ul> <li>prerenderingで動的ページのSEOも対応</li> <li>カスタムドメインやSSLもNetlifyでOK</li> <li>定期実行はCloud Functionsで。</li> <li>ただし、定期実行は1アカウントにつき、3ジョブまで</li> </ul></li> <li>SSRならHeroku <ul> <li>SSRで動的ページのSEOも対応</li> <li>定期実行はHeroku Schedulerで</li> <li>Cloudflareを併用し、SSL対応</li> <li>無料枠は1アカウントにつき、1アプリが限界かも</li> </ul></li> </ul> <p>SSRの方は他に<a target="_blank" rel="nofollow noopener" href="https://cloud.google.com/run?hl=ja">Cloud Run</a>や<a target="_blank" rel="nofollow noopener" href="https://zeit.co/">ZEIT now</a>がある。<br /> Cloud Runは立ち上げがGAEより遅いときので後手だけど、<br /> ZEIT nowは気になってるので試してみたい(<em>´ω`</em>)</p> <p>以上!!</p> <h2 id="こんなのつくってます!!"><a href="#%E3%81%93%E3%82%93%E3%81%AA%E3%81%AE%E3%81%A4%E3%81%8F%E3%81%A3%E3%81%A6%E3%81%BE%E3%81%99%21%21">こんなのつくってます!!</a></h2> <p>積読用の読書管理アプリ 『積読ハウマッチ』をリリースしました!<br /> <a target="_blank" rel="nofollow noopener" href="https://tsundoku.site">積読ハウマッチ</a>は、Nuxt.js+Firebaseで開発してます!</p> <p><img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/478782/572d4947-f40b-e4dc-1c9c-bc584cd2a66c.png" width="200"/></p> <p>もしよかったら、遊んでみてくださいヽ(=´▽`=)ノ</p> <p>要望・感想・アドバイスなどあれば、<br /> 公式アカウント(<a target="_blank" rel="nofollow noopener" href="https://twitter.com/MemoryLoverz">@MemoryLoverz</a>)や開発者(<a target="_blank" rel="nofollow noopener" href="https://twitter.com/kira_puka">@kira_puka</a>)まで!</p> <h3 id="へんあいマップもリリースしました!"><a href="#%E3%81%B8%E3%82%93%E3%81%82%E3%81%84%E3%83%9E%E3%83%83%E3%83%97%E3%82%82%E3%83%AA%E3%83%AA%E3%83%BC%E3%82%B9%E3%81%97%E3%81%BE%E3%81%97%E3%81%9F%EF%BC%81">へんあいマップもリリースしました!</a></h3> <p>「偏愛マップ」を簡単に作れるWebアプリです!<br /> <a target="_blank" rel="nofollow noopener" href="https://hen-ai.net">へんあいマップ</a>も、Nuxt.js+Firebaseで開発してます!</p> <p><img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/478782/63686f5c-390b-0b09-3f9a-0ce6676c3cce.png" width="600"/></p> <p>偏愛マップは人見知りや口下手な人にも優しいコミュニケーションツールで、<br /> 勉強会、懇親会、オフ会などの余興・アイスブレイクや自分のプロフィールにも!</p> <p>よかったら遊んでみてください(<em>´ω`</em>)</p> きらぷか@積読ハウマッチ/SSSAPIなど tag:crieit.net,2005:PublicArticle/15766 2020-03-14T22:03:11+09:00 2020-03-14T22:04:12+09:00 https://crieit.net/posts/1bbf25a7cba4095f0fd1afe817b553dd (改良版)パワプロ風画面ジェネレータを作ってみた <p><a href="https://crieit.now.sh/upload_images/102e92a702e89059033b1dff5b0f87c55e6cd23db0d46.jpg" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/102e92a702e89059033b1dff5b0f87c55e6cd23db0d46.jpg?mw=700" alt="" /></a></p> <h1 id="作ったサービス"><a href="#%E4%BD%9C%E3%81%A3%E3%81%9F%E3%82%B5%E3%83%BC%E3%83%93%E3%82%B9">作ったサービス</a></h1> <p><a target="_blank" rel="nofollow noopener" href="https://pawapro-gen.netlify.com/">https://pawapro-gen.netlify.com/</a></p> <h1 id="一旦書いたボード"><a href="#%E4%B8%80%E6%97%A6%E6%9B%B8%E3%81%84%E3%81%9F%E3%83%9C%E3%83%BC%E3%83%89">一旦書いたボード</a></h1> <p><a href="https://crieit.net/boards/web1week-202003/6fbd2929361eb9caa53fc33d51d31f36">web1week - パワプロ風画面ジェネレータを作ってみた</a></p> <p>時間的に残りの機能間に合わないかと思っていたので、最低限動く機能で出したのですが、結論から言うと間に合いました。</p> <h1 id="インライン編集機能"><a href="#%E3%82%A4%E3%83%B3%E3%83%A9%E3%82%A4%E3%83%B3%E7%B7%A8%E9%9B%86%E6%A9%9F%E8%83%BD">インライン編集機能</a></h1> <p>当初はフォームとプレビュー機能を分けて作っていたのですが、<br /> ほとんど全てインラインで編集できるように変更しました。</p> <h1 id="twitterシェア機能"><a href="#twitter%E3%82%B7%E3%82%A7%E3%82%A2%E6%A9%9F%E8%83%BD">twitterシェア機能</a></h1> <h2 id="OGPサーバの構築"><a href="#OGP%E3%82%B5%E3%83%BC%E3%83%90%E3%81%AE%E6%A7%8B%E7%AF%89">OGPサーバの構築</a></h2> <p>以前<a href="https://crieit.net/posts/slack">slack流量計</a>を作った際に、web表彰のスクリーンショットを撮っていたので、OGP作成にはそれほど抵抗はありませんでした。</p> <p>あとはだらさんの作成されてる<a target="_blank" rel="nofollow noopener" href="https://github.com/dala00/puppeteer-ogp">OGPサーバ</a>を参考にさせていただきました。</p> <h3 id="OGPサーバの構築に使った技術"><a href="#OGP%E3%82%B5%E3%83%BC%E3%83%90%E3%81%AE%E6%A7%8B%E7%AF%89%E3%81%AB%E4%BD%BF%E3%81%A3%E3%81%9F%E6%8A%80%E8%A1%93">OGPサーバの構築に使った技術</a></h3> <ul> <li>NodeJS</li> <li>Express</li> <li>TypeScript</li> <li>puppeteer</li> <li>VPS</li> <li>docker-compose</li> </ul> <h2 id="ロジック"><a href="#%E3%83%AD%E3%82%B8%E3%83%83%E3%82%AF">ロジック</a></h2> <ul> <li>ジェネレータからDBにデータを登録する</li> <li>登録されたデータをもとにpuppeteerでスクリーンショットを撮る</li> <li>DBに画像を格納する</li> <li>OGPのサーバ問い合わせにはDBに保存してある画像を使用する <ul> <li>OGPサーバに直接アクセスさせると負荷が集中してサーバが落ちるため</li> </ul></li> </ul> <h3 id="DB登録処理は既存のAPサーバに追加"><a href="#DB%E7%99%BB%E9%8C%B2%E5%87%A6%E7%90%86%E3%81%AF%E6%97%A2%E5%AD%98%E3%81%AEAP%E3%82%B5%E3%83%BC%E3%83%90%E3%81%AB%E8%BF%BD%E5%8A%A0">DB登録処理は既存のAPサーバに追加</a></h3> <h3 id="OGPサーバは別コンテナ"><a href="#OGP%E3%82%B5%E3%83%BC%E3%83%90%E3%81%AF%E5%88%A5%E3%82%B3%E3%83%B3%E3%83%86%E3%83%8A">OGPサーバは別コンテナ</a></h3> <h2 id="twitter投稿"><a href="#twitter%E6%8A%95%E7%A8%BF">twitter投稿</a></h2> <p>アプリ起動時<code>componentDidMount</code>のタイミングでUUIDを生成するようにしました。<br /> <a target="_blank" rel="nofollow noopener" href="https://www.npmjs.com/package/react-share">react-share</a>というライブラリを使い、<br /> <code>beforeOnClick</code>メソッドでDBへの登録を行っています。<br /> ローディング画面も作りました(同時にバグ生成)。</p> <pre><code class="html"><TwitterShareButton url={'https://pawapro-gen.netlify.com/view/'+store.getState().uuid} hashtags={['パワプロ風画面ジェネレータで作ってみた']} beforeOnClick={async()=>{ store.dispatch(switchLoading(true)); let isSuccess = await postData(); store.dispatch(switchLoading(false)); if(!isSuccess){ alert('データの保存に失敗しました。時間を空けるか、リロードして再度作成してください。') return new Error(); }else{ return Promise.resolve(); } <span>}</span><span>}</span> > 保存してシェアする! </TwitterShareButton> </code></pre> <h1 id="苦労したこと"><a href="#%E8%8B%A6%E5%8A%B4%E3%81%97%E3%81%9F%E3%81%93%E3%81%A8">苦労したこと</a></h1> <h2 id="画像のキャッシュ"><a href="#%E7%94%BB%E5%83%8F%E3%81%AE%E3%82%AD%E3%83%A3%E3%83%83%E3%82%B7%E3%83%A5">画像のキャッシュ</a></h2> <p>ヘッダでキャッシュコントロールしたことがなかったので若干はまりました。</p> <h2 id="OGPのトリミング問題"><a href="#OGP%E3%81%AE%E3%83%88%E3%83%AA%E3%83%9F%E3%83%B3%E3%82%B0%E5%95%8F%E9%A1%8C">OGPのトリミング問題</a></h2> <p>twitterやFacebookでOGPの上下左右がトリミングされる問題があり、<br /> Jimpなどで余白の拡張をしようと思ったのですが、時間がなかったこともあり、<br /> React側でマージンをとってスクリーンショットを撮る際に余白ができるように修正しました。</p> <h1 id="できなかったこと"><a href="#%E3%81%A7%E3%81%8D%E3%81%AA%E3%81%8B%E3%81%A3%E3%81%9F%E3%81%93%E3%81%A8">できなかったこと</a></h1> <h2 id="チーム・選手画像のアップロード"><a href="#%E3%83%81%E3%83%BC%E3%83%A0%E3%83%BB%E9%81%B8%E6%89%8B%E7%94%BB%E5%83%8F%E3%81%AE%E3%82%A2%E3%83%83%E3%83%97%E3%83%AD%E3%83%BC%E3%83%89">チーム・選手画像のアップロード</a></h2> <p>OGPの生成で手一杯だったので、base64化してDBに格納するところまで考えられませんでした。後々追加する予定。</p> ckoshien tag:crieit.net,2005:PublicArticle/15749 2020-03-06T09:34:49+09:00 2020-03-07T09:31:54+09:00 https://crieit.net/posts/netlify-redirect-nuxt nuxt.jsプロジェクトをnetlifyで公開してredirectを使うときの罠 <hr /> <p>3/6 11:15追記</p> <p>サブディレクトリにあるときだけ発生する問題ぽい。問題をまとめてサポートに報告しました https://community.netlify.com/t/subdirectory-and-publish-directory-redirection/10120</p> <hr /> <p>netlifyにはnetlity.tomlという設定ファイルを書くといろいろ設定ができることになっていて、従来nginxでやっていたような各種リダイレクトも、ここに書くことで実行してくれることになっている。<br /> <a target="_blank" rel="nofollow noopener" href="https://docs.netlify.com/configure-builds/file-based-configuration/#redirects">Syntax for the Netlify configuration file</a></p> <p>これと、nuxt.jsみたいにビルド結果をサブディレクトリに出力するツールが組み合わさるとちょっと予想しない対応が必要になる話。</p> <hr /> <p>ルートディレクトリに配置したnetlify.tomlにビルドとリダイレクトの設定を書く。</p> <pre><code>[build] base = "front/" publish = "dist/" command = "npm run generate" [[redirects]] from = "https://gisyohub.netlify.com/" to = "https://gisyohub.com/" status = 301 force = true </code></pre> <p>結果、ビルドはしてくれるのだけれど、リダイレクトは動かない。</p> <p>ぼくの実験結果だと、同じファイルをコピーして、staticディレクトリにもnetlify.tomlを配置してあげると、ビルドする時dist/にコピーされて、その結果redirectが動作するようになる。</p> <p>おそらくnetlifyは、ビルド時はルートディレクトリにあるnetlify.tomlを見るけど、リダイレクトのときはpublishディレクトリにあるnetlify.tomlを見ているらしい。実装の都合は何となく分かるんだけど、せめてドキュメントに明示してほしい感じ。</p> <p>こことかここで話題に上がってるんだけど、バグとしては認めてもらえなかったぽい。<br /> <a target="_blank" rel="nofollow noopener" href="https://community.netlify.com/t/redirects-within-netlify-toml-not-working/1731">https://community.netlify.com/t/redirects-within-netlify-toml-not-working/1731</a><br /> <a target="_blank" rel="nofollow noopener" href="https://community.netlify.com/t/netlify-toml-folder-with-nuxtjs-and-netlify-cli/4659/3">https://community.netlify.com/t/netlify-toml-folder-with-nuxtjs-and-netlify-cli/4659/3</a></p> <p>シンプル再現環境を作ればバグだと認めてもらえそうな気がするんだけど、この問題のミニマム環境って何で作るのがいいのだろう。nuxtとかの開発環境なしに、index.htmlだけをnetlifyで公開するような構成って作れるのかな?</p> daisuke furukawa tag:crieit.net,2005:PublicArticle/15711 2020-02-06T23:52:50+09:00 2020-02-06T23:52:50+09:00 https://crieit.net/posts/Vue-js-Netlify Vue.js+Netlifyで最小のアプリを作る <p>Vue.jsで最小のアプリケーションを作成して、Netlifyにデプロイするまでを解説します。1週間以内くらいで1ページだけの簡単なWebアプリケーションを作ろう、と思ったらこれくらいで十分可能です。複雑なアプリケーションにしなければデータベースもブラウザ上のLocal Storageに保存する形であれば用意する必要もありませんし、とても楽です。</p> <h2 id="準備するもの"><a href="#%E6%BA%96%E5%82%99%E3%81%99%E3%82%8B%E3%82%82%E3%81%AE">準備するもの</a></h2> <p>今回の最小のアプリケーションを作成するためには下記の準備が必要です。インストールやユーザー登録をしておきましょう。</p> <p>npmのインストール<br /> Yarn(好みで必要であれば)<br /> GitHubのユーザー登録<br /> Netlifyのユーザー登録</p> <h2 id="Vue CLI"><a href="#Vue+CLI">Vue CLI</a></h2> <p>まずはPCにVue CLIをインストールします。Vue CLIを利用すると、コマンドで簡単にVue.jsのプロジェクトを作成できるようになります。インストール方法は下記のどちらかのコマンドです。</p> <pre><code>npm install -g @vue/cli </code></pre> <p>もしくは</p> <pre><code>yarn global add @vue/cli </code></pre> <h2 id="プロジェクトを新規作成"><a href="#%E3%83%97%E3%83%AD%E3%82%B8%E3%82%A7%E3%82%AF%E3%83%88%E3%82%92%E6%96%B0%E8%A6%8F%E4%BD%9C%E6%88%90">プロジェクトを新規作成</a></h2> <p>Vue CLIを使ってVue.jsのプロジェクトを新規登録します。下記のコマンドを実行します。</p> <pre><code>vue create hello-world </code></pre> <p>するとプロジェクトにどのような機能を入れるかを選択しながら作成することが出来ます。必要なものを選択していきます。よく分からない場合は最初にdefaultを選択しておけば良いと思います。</p> <p>例として、僕はManually select featuresで下記を選択しました。</p> <h3 id="Check the features needed for your project"><a href="#Check+the+features+needed+for+your+project">Check the features needed for your project</a></h3> <p>Babel, TypeScript, Linter/Formatter, Unit Testing, E2E Testing</p> <p>TypeScriptと2つのTestingを追加しました。1ページの軽いアプリケーションなのでRouterは選択しませんでした。Testingは今回は使いませんが、もし使うことになった際に一から設定するのは面倒なのでとりあえず雛形を作ってもらっておいたほうが楽かなと思いいつも追加しています。とりあえずシンプルなアプリケーションを作るだけであれば不要でしょう。</p> <h3 id="Use class-style component syntax?"><a href="#Use+class-style+component+syntax%3F">Use class-style component syntax?</a></h3> <p>Vueコンポーネントの使い方が変わります。好きな形を選択しましょう。Vue.js3でまた新しいカタチも出てくるのですがまだ選択できませんのでとりあえず好きなもので良いと思います。シンプルなアプリケーション用ですのであまり考える必要はないでしょう。</p> <h3 id="Use Babel alongside TypeScript"><a href="#Use+Babel+alongside+TypeScript">Use Babel alongside TypeScript</a></h3> <p>TypeScriptの場合? だけでしょうか。そのままYで良いと思います。</p> <h3 id="Pick a linter / formatter config"><a href="#Pick+a+linter+%2F+formatter+config">Pick a linter / formatter config</a></h3> <p>これもLinter/Formatterを選択している場合だけでしょうか。TSLintはもう使わないほうが良いのでそれ以外であれば好きなのを選べば良いと思います。僕はPrettierを選んでいます。</p> <h3 id="Pick additional lint features"><a href="#Pick+additional+lint+features">Pick additional lint features</a></h3> <p>いつLintするかの設定です。とりあえずLint on saveで良いと思います。</p> <h3 id="Pick a unit testing solution"><a href="#Pick+a+unit+testing+solution">Pick a unit testing solution</a></h3> <p>Unitテストで利用するパッケージの選択です。GitHubでのスター数が多いので適当にいつもJestにしています。</p> <h3 id="Pick a E2E testing solution"><a href="#Pick+a+E2E+testing+solution">Pick a E2E testing solution</a></h3> <p>同上でcypressを選択しました。</p> <h3 id="Where do you prefer placing config for Babel, ESLint, etc.?"><a href="#Where+do+you+prefer+placing+config+for+Babel%2C+ESLint%2C+etc.%3F">Where do you prefer placing config for Babel, ESLint, etc.?</a></h3> <p>いろんな設定がpackage.jsonに詰め込まれるのは嫌なのでIn dedicated config filesにしました。</p> <h3 id="Save this as a preset for future projects?"><a href="#Save+this+as+a+preset+for+future+projects%3F">Save this as a preset for future projects?</a></h3> <p>最後にこの設定を今後も使うかの選択です。怖いのでそのままNにしました。</p> <p>これで完了です。待っていれば先程指定したアプリケーション名のフォルダにプロジェクトが作成されます。E2Eテストとかを入れているとかなり時間がかかるかもしれません。</p> <p>問題なければ最後に表示されるように、cdコマンドでフォルダ内に移動し、serveコマンドを実行すればもうアプリケーションが実行できます。</p> <pre><code>cd vue-netlify-sample yarn serve </code></pre> <p>問題なければURLが表示されるのでアクセスすれば表示されます。</p> <p><a href="https://crieit.now.sh/upload_images/6e2af66c610d88bc766649f72032893a5e3c22357df8f.png" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/6e2af66c610d88bc766649f72032893a5e3c22357df8f.png?mw=700" alt="" /></a></p> <h2 id="Gitにコミットする"><a href="#Git%E3%81%AB%E3%82%B3%E3%83%9F%E3%83%83%E3%83%88%E3%81%99%E3%82%8B">Gitにコミットする</a></h2> <p>とりあえずプロジェクトを作成した直後の状態をGitにコミットしておくと良いでしょう。通常は<code>git init</code>する必要がありますが、Vue CLIの場合はそれもやってくれているっぽいのでそのままコミットできます。<code>git add</code>も必要ですがそれもやってくれているっぽいので、例えばVSCodeの場合はそのままメッセージを入力してコミットできるっぽいです。</p> <p><a href="https://crieit.now.sh/upload_images/6e2af66c610d88bc766649f72032893a5e3c23103f0ba.png" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/6e2af66c610d88bc766649f72032893a5e3c23103f0ba.png?mw=700" alt="" /></a></p> <h2 id="GitHubにリポジトリをpushする"><a href="#GitHub%E3%81%AB%E3%83%AA%E3%83%9D%E3%82%B8%E3%83%88%E3%83%AA%E3%82%92push%E3%81%99%E3%82%8B">GitHubにリポジトリをpushする</a></h2> <p>さきほど作成したリポジトリとそのコミットを、GitHubのリモートリポジトリに登録します。まずは新しいリポジトリをGitHub上に作成します。</p> <p><a href="https://crieit.now.sh/upload_images/6e2af66c610d88bc766649f72032893a5e2edb295a7d4.png" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/6e2af66c610d88bc766649f72032893a5e2edb295a7d4.png?mw=700" alt="" /></a></p> <p>設定はこんな感じです。名前だけ入力するだけで大丈夫です。公開したくなければPrivateを選択しておきましょう。</p> <p><a href="https://crieit.now.sh/upload_images/6e2af66c610d88bc766649f72032893a5e3c23ac4b13b.png" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/6e2af66c610d88bc766649f72032893a5e3c23ac4b13b.png?mw=700" alt="image.png" /></a></p> <p>新規作成できると、空のリポジトリが作成されます。この状態ではリポジトリの初期化方法が色々と書かれています。その中に既存のローカルリポジトリを使う方法が書かれていますので、それをローカルのプロジェクト上で実行します。</p> <pre><code>git remote add origin [email protected]:dala00/vue-netlify-sample.git git push -u origin master </code></pre> <p>問題なければGitHub上の画面を更新するとpushしたファイル一覧が表示されるようになります。</p> <h2 id="Netlifyにデプロイする"><a href="#Netlify%E3%81%AB%E3%83%87%E3%83%97%E3%83%AD%E3%82%A4%E3%81%99%E3%82%8B">Netlifyにデプロイする</a></h2> <p>続いてNetlifyで先程作成したGitHubのリポジトリを連携させて、簡単にデプロイできるようにします。まずは「New site from Git」ボタンを実行します。するとGitHub上のリポジトリ一覧が表示されます。</p> <p>ただ、新規登録時やNetlify上でのアプリケーション作成時のGitHub連携方法によってはもしかしたら表示されていないかもしれません。すべてのリポジトリの連携を許可する設定であれば表示されていると思いますが、特定のリポジトリだけ許可する設定で連携した場合は作成したばかりのGitHubリポジトリは表示されていません。その場合は画面の下に</p> <pre><code>Can’t see your repo here? Configure the Netlify app on GitHub. </code></pre> <p>というメッセージのリンクがありますので、そちらでGitHubに移動して許可するリポジトリを追加する必要があります。下記のような感じで追加します。</p> <p><a href="https://crieit.now.sh/upload_images/6e2af66c610d88bc766649f72032893a5e3c248c97082.png" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/6e2af66c610d88bc766649f72032893a5e3c248c97082.png?mw=700" alt="" /></a></p> <p>問題なく選択できたらアプリケーションの新規登録画面に遷移します。Build commandに<code>yarn build</code>、Publish directoryに<code>dist</code>と入力して登録を行います。</p> <p>登録が完了するとデプロイ一覧に下記のようにデプロイ中である旨のステータスが表示されています。完了すると緑色のPublishedというステータスに変わるのでしばらく待ちましょう。</p> <p><a href="https://crieit.now.sh/upload_images/6e2af66c610d88bc766649f72032893a5e2edcb5e368f.png" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/6e2af66c610d88bc766649f72032893a5e2edcb5e368f.png?mw=700" alt="" /></a></p> <p>問題なければ上部にURLが表示され、アクセスできるようになります。下記が実際にデプロイしたURLです。</p> <p><a target="_blank" rel="nofollow noopener" href="https://eager-neumann-0e6dfa.netlify.com/">https://eager-neumann-0e6dfa.netlify.com/</a></p> <p>eager-neumann-0e6dfaの部分はSite settingsのsite nameという項目で変更することもできます。独自ドメインを持っていればそれを利用することもできます。</p> <h2 id="アプリケーションを更新する"><a href="#%E3%82%A2%E3%83%97%E3%83%AA%E3%82%B1%E3%83%BC%E3%82%B7%E3%83%A7%E3%83%B3%E3%82%92%E6%9B%B4%E6%96%B0%E3%81%99%E3%82%8B">アプリケーションを更新する</a></h2> <p>アプリケーションに機能を追加してそれをデプロイしたい場合は、追加分をコミットしてGitHubにpushすると自動的にNetlify側も更新してくれます。</p> <p>例えば試しに下記のようなよくあるインクリメントのテスト機能を書いてみます。例えばTypeScriptの場合はApp.vueを下記のように変更します。(主にcountを使ったところを追記しています)</p> <pre><code class="html"><template> <div id="app"> <img alt="Vue logo" src="./assets/logo.png" /> <HelloWorld msg="Welcome to Your Vue.js + TypeScript App" /> <div> <button @click="increment">Increment</button> </div> <div><span>{</span><span>{</span> count <span>}</span><span>}</span></div> </div> </template> <script lang="ts"> import { Component, Vue } from 'vue-property-decorator' import HelloWorld from './components/HelloWorld.vue' @Component({ components: { HelloWorld } }) export default class App extends Vue { count = 0 increment() { this.count++ } } </script> </code></pre> <p>問題なければ下記のようにボタンを押すと数値が増えていく機能が出来上がります。</p> <p><a href="https://crieit.now.sh/upload_images/6e2af66c610d88bc766649f72032893a5e3c27d6e740a.png" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/6e2af66c610d88bc766649f72032893a5e3c27d6e740a.png?mw=700" alt="" /></a></p> <p>あとはコミットしてpushすると再度Netlify上でデプロイが走りアプリケーション上に反映されます。</p> <h2 id="まとめ"><a href="#%E3%81%BE%E3%81%A8%E3%82%81">まとめ</a></h2> <p>このようにJavaScriptとGitHub、Netlifyを使うだけだと非常に簡単にアプリケーションを作ることができます。サンプルや、ずっと運用していくわけではない簡単なアプリケーションであればおすすめです。</p> <p>今回作成したプロジェクトはGitHubで公開しています。</p> <p><a target="_blank" rel="nofollow noopener" href="https://github.com/dala00/vue-netlify-sample">https://github.com/dala00/vue-netlify-sample</a></p> だら@Crieit開発者 tag:crieit.net,2005:PublicArticle/15700 2020-01-27T22:30:38+09:00 2020-01-27T22:30:38+09:00 https://crieit.net/posts/React-Netlify React+Netlifyで最小のアプリを作る <p>Reactで最小のアプリケーションを作成して、Netlifyにデプロイするまでを解説します。1週間以内くらいで1ページだけの簡単なWebアプリケーションを作ろう、と思ったらこれくらいで十分可能です。複雑なアプリケーションにしなければデータベースもブラウザ上のLocal Storageに保存する形であれば用意する必要もありませんし、とても楽です。</p> <h2 id="準備するもの"><a href="#%E6%BA%96%E5%82%99%E3%81%99%E3%82%8B%E3%82%82%E3%81%AE">準備するもの</a></h2> <p>今回の最小のアプリケーションを作成するためには下記の準備が必要です。インストールやユーザー登録をしておきましょう。</p> <ul> <li>npmのインストール</li> <li>Yarn(好みで必要であれば)</li> <li>GitHubのユーザー登録</li> <li>Netlifyのユーザー登録</li> </ul> <p>Node.jsはnvm等を使うと複数のバージョンをインストールできて便利です(が、慣れてからでも良いと思います)。</p> <h2 id="プロジェクトを新規作成"><a href="#%E3%83%97%E3%83%AD%E3%82%B8%E3%82%A7%E3%82%AF%E3%83%88%E3%82%92%E6%96%B0%E8%A6%8F%E4%BD%9C%E6%88%90">プロジェクトを新規作成</a></h2> <p>まずはReact.jsのプロジェクトを新規登録します。npmやYarnが入っていればコマンド一発で新規作成できます。下記のどちらかのコマンドです。</p> <pre><code>npx create-react-app my-app </code></pre> <pre><code>yarn create react-app my-app </code></pre> <p>TypeScriptを使いたい場合はパラメータを指定するだけで可能です。</p> <pre><code>npx create-react-app my-app --template typescript </code></pre> <pre><code>yarn create react-app my-app --template typescript </code></pre> <p>完了したらもうすぐに下記コマンドで実行できます。</p> <pre><code>cd my-app </code></pre> <p>で作成されたフォルダに入り、下記のどちらかのコマンドを実行します。</p> <pre><code>npm run start </code></pre> <pre><code>yarn start </code></pre> <p>問題なければ下記のような画面が表示されます。</p> <p><a href="https://crieit.now.sh/upload_images/6e2af66c610d88bc766649f72032893a5e2ed7253d6c4.png" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/6e2af66c610d88bc766649f72032893a5e2ed7253d6c4.png?mw=700" alt="" /></a></p> <h2 id="Gitにコミットする"><a href="#Git%E3%81%AB%E3%82%B3%E3%83%9F%E3%83%83%E3%83%88%E3%81%99%E3%82%8B">Gitにコミットする</a></h2> <p>とりあえずプロジェクトを作成した直後の状態をGitにコミットしておくと良いでしょう。下記のコマンドで空のリポジトリを作って初期化します。</p> <pre><code>git init </code></pre> <p>あとはVSCode等を使っていればプラスボタンでファイルを <code>git add</code> と同じようにステージングできます。そしてコメントを入力してチェックボタンをクリックすると <code>git commit</code> がVSCode上で実行できます。</p> <p><a href="https://crieit.now.sh/upload_images/6e2af66c610d88bc766649f72032893a5e2ed8c9799dd.png" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/6e2af66c610d88bc766649f72032893a5e2ed8c9799dd.png?mw=700" alt="" /></a></p> <h2 id="GitHubにリポジトリをpushする"><a href="#GitHub%E3%81%AB%E3%83%AA%E3%83%9D%E3%82%B8%E3%83%88%E3%83%AA%E3%82%92push%E3%81%99%E3%82%8B">GitHubにリポジトリをpushする</a></h2> <p>さきほど作成したリポジトリとそのコミットを、GitHubのリモートリポジトリに登録します。まずは新しいリポジトリをGitHub上に作成します。</p> <p><a href="https://crieit.now.sh/upload_images/6e2af66c610d88bc766649f72032893a5e2edb295a7d4.png" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/6e2af66c610d88bc766649f72032893a5e2edb295a7d4.png?mw=700" alt="" /></a></p> <p>設定はこんな感じです。名前だけ入力するだけで大丈夫です。公開したくなければPrivateを選択しておきましょう。</p> <p><a href="https://crieit.now.sh/upload_images/6e2af66c610d88bc766649f72032893a5e2edb53e1764.png" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/6e2af66c610d88bc766649f72032893a5e2edb53e1764.png?mw=700" alt="" /></a></p> <p>新規作成できると、空のリポジトリが作成されます。この状態ではリポジトリの初期化方法が色々と書かれています。その中に既存のローカルリポジトリを使う方法が書かれていますので、それをローカルのプロジェクト上で実行します。</p> <pre><code>git remote add origin [email protected]:dala00/react-netlify-sample.git git push -u origin master </code></pre> <p>問題なければGitHub上の画面を更新するとpushしたファイル一覧が表示されるようになります。</p> <h2 id="Netlifyにデプロイする"><a href="#Netlify%E3%81%AB%E3%83%87%E3%83%97%E3%83%AD%E3%82%A4%E3%81%99%E3%82%8B">Netlifyにデプロイする</a></h2> <p>続いてNetlifyで先程作成したGitHubのリポジトリを連携させて、簡単にデプロイできるようにします。まずは「New site from Git」ボタンを実行します。するとGitHub上のリポジトリ一覧が表示されます。</p> <p>ただ、新規登録時やNetlify上でのアプリケーション作成時のGitHub連携方法によってはもしかしたら表示されていないかもしれません。すべてのリポジトリの連携を許可する設定であれば表示されていると思いますが、特定のリポジトリだけ許可する設定で連携した場合は作成したばかりのGitHubリポジトリは表示されていません。その場合は画面の下に</p> <pre><code>Can’t see your repo here? Configure the Netlify app on GitHub. </code></pre> <p>というメッセージのリンクがありますので、そちらでGitHubに移動して許可するリポジトリを追加する必要があります。下記のような感じで追加します。</p> <p><a href="https://crieit.now.sh/upload_images/6e2af66c610d88bc766649f72032893a5e2edc4137941.png" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/6e2af66c610d88bc766649f72032893a5e2edc4137941.png?mw=700" alt="" /></a></p> <p>問題なく選択できたらアプリケーションの新規登録画面に遷移します。最近は下記のようにビルドコマンドや公開フォルダも自動的に入力されていますので、本当に何もする必要がありません。そのまま登録を行います。</p> <p><a href="https://crieit.now.sh/upload_images/6e2af66c610d88bc766649f72032893a5e2edc85af183.png" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/6e2af66c610d88bc766649f72032893a5e2edc85af183.png?mw=700" alt="" /></a></p> <p>登録が完了するとデプロイ一覧に下記のようにデプロイ中である旨のステータスが表示されています。完了すると緑色のPublishedというステータスに変わるのでしばらく待ちましょう。</p> <p><a href="https://crieit.now.sh/upload_images/6e2af66c610d88bc766649f72032893a5e2edcb5e368f.png" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/6e2af66c610d88bc766649f72032893a5e2edcb5e368f.png?mw=700" alt="" /></a></p> <p>問題なければ上部にURLが表示され、アクセスできるようになります。下記が実際にデプロイしたURLです。</p> <p><a target="_blank" rel="nofollow noopener" href="https://agitated-noyce-250fb9.netlify.com/">https://agitated-noyce-250fb9.netlify.com/</a></p> <p>agitated-noyce-250fb9の部分はSite settingsのsite nameという項目で変更することもできます。独自ドメインを持っていればそれを利用することもできます。</p> <h2 id="アプリケーションを更新する"><a href="#%E3%82%A2%E3%83%97%E3%83%AA%E3%82%B1%E3%83%BC%E3%82%B7%E3%83%A7%E3%83%B3%E3%82%92%E6%9B%B4%E6%96%B0%E3%81%99%E3%82%8B">アプリケーションを更新する</a></h2> <p>アプリケーションに機能を追加してそれをデプロイしたい場合は、追加分をコミットしてGitHubにpushすると自動的にNetlify側も更新してくれます。</p> <p>例えば試しに下記のようなよくあるインクリメントのテスト機能を書いてみます。例えばTypeScriptの場合はApp.tsxを下記のように変更します。(useStateと、そこから生成したcount, setCountを使っているところを追記しています)</p> <pre><code class="jsx">import React, { useState } from 'react' import logo from './logo.svg' import './App.css' const App: React.FC = () => { const [count, setCount] = useState(1) return ( <div className="App"> <header className="App-header"> <img src={logo} className="App-logo" alt="logo" /> <p> Edit <code>src/App.tsx</code> and save to reload. </p> <a className="App-link" href="https://reactjs.org" target="_blank" rel="noopener noreferrer" > Learn React </a> <div>{count}</div> <div> <button onClick={() => setCount(count + 1)}>Increment</button> </div> </header> </div> ) } export default App </code></pre> <p>問題なければ下記のようにボタンを押すと数値が増えていく機能が出来上がります。</p> <p><a href="https://crieit.now.sh/upload_images/6e2af66c610d88bc766649f72032893a5e2ee4f9e2ecf.png" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/6e2af66c610d88bc766649f72032893a5e2ee4f9e2ecf.png?mw=700" alt="" /></a></p> <p>あとはコミットしてpushすると再度Netlify上でデプロイが走りアプリケーション上に反映されます。</p> <h2 id="まとめ"><a href="#%E3%81%BE%E3%81%A8%E3%82%81">まとめ</a></h2> <p>このようにJavaScriptとGitHub、Netlifyを使うだけだと非常に簡単にアプリケーションを作ることができます。サンプルや、ずっと運用していくわけではない簡単なアプリケーションであればおすすめです。</p> <p>今回作成したプロジェクトはGitHubで公開しています。</p> <p><a target="_blank" rel="nofollow noopener" href="https://github.com/dala00/react-netlify-sample">https://github.com/dala00/react-netlify-sample</a></p> だら@Crieit開発者 tag:crieit.net,2005:PublicArticle/15666 2020-01-07T21:31:01+09:00 2020-01-07T21:31:01+09:00 https://crieit.net/posts/SPA-Nuxt-Firebase-Hosting-Netlify SPAなNuxtアプリをFirebase HostingからNetlifyに移行してみた <p>Netlifyがいいと聞いて、Firebase Hostingから移行してみたときの備忘録。</p> <p>NetfilyのプレレンダリングがあるのでOGP芸が楽。<br /> 昔、<a target="_blank" rel="nofollow noopener" href="https://www.memory-lovers.blog/entry/2019/08/07/150000">こんな記事</a>を書いたけど、Functionsで頑張らなくてもよくなった。</p> <p>使ってみてよかったので、移行したときにやったことをまとめてみる。</p> <h2 id="移行するときにやったこと"><a href="#%E7%A7%BB%E8%A1%8C%E3%81%99%E3%82%8B%E3%81%A8%E3%81%8D%E3%81%AB%E3%82%84%E3%81%A3%E3%81%9F%E3%81%93%E3%81%A8">移行するときにやったこと</a></h2> <h4 id="nuxt.config.tsなどの変更"><a href="#nuxt.config.ts%E3%81%AA%E3%81%A9%E3%81%AE%E5%A4%89%E6%9B%B4">nuxt.config.tsなどの変更</a></h4> <h5 id="1. Nuxtなどの公式サイトを見て設定"><a href="#1.+Nuxt%E3%81%AA%E3%81%A9%E3%81%AE%E5%85%AC%E5%BC%8F%E3%82%B5%E3%82%A4%E3%83%88%E3%82%92%E8%A6%8B%E3%81%A6%E8%A8%AD%E5%AE%9A">1. Nuxtなどの公式サイトを見て設定</a></h5> <p>Firebase Hostingで設定したのとかぶっているところもあり、nuxt.config.tsは変更なし。</p> <p>参考: <a target="_blank" rel="nofollow noopener" href="https://ja.nuxtjs.org/faq/netlify-deployment/">Netlify へデプロイするには? - NuxtJS</a></p> <h5 id="2. _redirectsファイルの作成"><a href="#2.+_redirects%E3%83%95%E3%82%A1%E3%82%A4%E3%83%AB%E3%81%AE%E4%BD%9C%E6%88%90">2. _redirectsファイルの作成</a></h5> <p>Netlifyでは、index.html以外は404になるので、<br /> SPAの場合は<code>static/_redirecs</code>というファイルを作成して、<br /> 以下の内容を記載しておく。</p> <pre><code>/* /index.html 200 </code></pre> <p>参考] <a target="_blank" rel="nofollow noopener" href="https://docs.netlify.com/routing/redirects/rewrites-proxies/#history-pushstate-and-single-page-apps">Rewrites and proxies | Netlify Docs</a></p> <h5 id="3. Netlify側の設定"><a href="#3.+Netlify%E5%81%B4%E3%81%AE%E8%A8%AD%E5%AE%9A">3. Netlify側の設定</a></h5> <p>あとは、Netlifyにアクセスして、 「<a target="_blank" rel="nofollow noopener" href="https://ja.nuxtjs.org/faq/netlify-deployment/#spa-%E3%83%A2%E3%83%BC%E3%83%89%E3%81%A7%E7%94%9F%E6%88%90%E3%81%95%E3%82%8C%E3%81%9F%E3%82%B5%E3%82%A4%E3%83%88%E3%81%AE%E5%A0%B4%E5%90%88">SPA モードで生成されたサイトの場合</a>」にも書いてあるとおりの内容をNetlifyの設定に入力すればOK。</p> <p>迷わなければ、5分くらいでできて、masterにpushと更新される(<em>´ω`</em>)</p> <h4 id="プレレンダリング"><a href="#%E3%83%97%E3%83%AC%E3%83%AC%E3%83%B3%E3%83%80%E3%83%AA%E3%83%B3%E3%82%B0">プレレンダリング</a></h4> <p>ベータ版だけど、Netlifyでプレレンダリングしてくれる機能がある。</p> <p><a target="_blank" rel="nofollow noopener" href="https://www.memory-lovers.blog/entry/2019/08/07/150000">この記事</a>に書いてある感じで、<br /> 「FunctionsでheadだけのHTMLを生成→リダイレクト」<br /> としていたのが不要になる感じ。</p> <h5 id="Netlifyの画面で設定"><a href="#Netlify%E3%81%AE%E7%94%BB%E9%9D%A2%E3%81%A7%E8%A8%AD%E5%AE%9A">Netlifyの画面で設定</a></h5> <p>設定は簡単で、Netlify上で設定画面で有効化すればOK。<br /> 参考: <a target="_blank" rel="nofollow noopener" href="https://docs.netlify.com/site-deploys/post-processing/prerendering/#set-up-prerendering">Prerendering | Netlify Docs</a></p> <h5 id="その他の修正点"><a href="#%E3%81%9D%E3%81%AE%E4%BB%96%E3%81%AE%E4%BF%AE%E6%AD%A3%E7%82%B9">その他の修正点</a></h5> <p>あとは、以下の修正。</p> <ol> <li>OPGのためにrouterでリダイレクトしていた設定を削除</li> <li>各ページでメタ情報を適切になるように変更</li> </ol> <p>また、上で書いたとおり、<code>static/_redirecs</code>で</p> <pre><code>/* /index.html 200 </code></pre> <p><strong>これを設定しておかないとボットがアクセスしたときに404になってOGPがでない</strong>。。</p> <p>(これに気づかずかなりハマった。。)</p> <h5 id="小ネタ: プレレンダリングの結果を確認する"><a href="#%E5%B0%8F%E3%83%8D%E3%82%BF%3A+%E3%83%97%E3%83%AC%E3%83%AC%E3%83%B3%E3%83%80%E3%83%AA%E3%83%B3%E3%82%B0%E3%81%AE%E7%B5%90%E6%9E%9C%E3%82%92%E7%A2%BA%E8%AA%8D%E3%81%99%E3%82%8B">小ネタ: プレレンダリングの結果を確認する</a></h5> <p>プレレンダリングするかどうかは、UAを見て判断しているらしく、</p> <ul> <li>UAを指定してすると、プレレンダリングされた結果を確認でき、</li> <li>UAを指定してないと、プレレンダリングなしの結果になる</li> </ul> <p>curlだとこんな感じで、<code>-A twitterbot</code>を指定するとよい。</p> <pre><code class="shell">$ curl -A twitterbot https://example.com/hoge/fuga -o fuga.html </code></pre> <p>参考: <a target="_blank" rel="nofollow noopener" href="https://community.netlify.com/t/common-issue-understanding-and-debugging-prerendering/150">[Common Issue] Understanding and debugging prerendering</a></p> <p>毎回、<a target="_blank" rel="nofollow noopener" href="https://cards-dev.twitter.com/validator">Twitter Card Validator</a>で確認するのはめんどくさいので、この方法だと便利。</p> <h3 id="ページが存在しないURLの対応"><a href="#%E3%83%9A%E3%83%BC%E3%82%B8%E3%81%8C%E5%AD%98%E5%9C%A8%E3%81%97%E3%81%AA%E3%81%84URL%E3%81%AE%E5%AF%BE%E5%BF%9C">ページが存在しないURLの対応</a></h3> <p>積読ハウマッチでは、少し特殊なことをしていて、<br /> 一部のURLはheadだけのHTMLしか存在しないページがいくつかある。</p> <p>なので、その部分を修正していく。方法としては以下の感じ。</p> <ol> <li>nuxt.config.tsでrouterに、クエリパラメタ付きでリダイレクトを追加する <ul> <li><a target="_blank" rel="nofollow noopener" href="https://router.vuejs.org/ja/guide/essentials/redirect-and-alias.html">リダイレクトとエイリアス | Vue Router</a></li> </ul></li> <li><code>asyncData()</code>でクエリパラメタを受け取り、<code>created()</code>で削除 <ul> <li><code>this.$router.push({ query: {} });</code></li> <li><a target="_blank" rel="nofollow noopener" href="https://www.kimullaa.com/entry/2018/01/27/183743">Vuejs vue-router クエリパラメータの一部だけを取り除く - SIerだけど技術やりたいブログ</a></li> </ul></li> </ol> <h4 id="動的なサイトマップの対応"><a href="#%E5%8B%95%E7%9A%84%E3%81%AA%E3%82%B5%E3%82%A4%E3%83%88%E3%83%9E%E3%83%83%E3%83%97%E3%81%AE%E5%AF%BE%E5%BF%9C">動的なサイトマップの対応</a></h4> <p>書籍やユーザのページなどは日々増えていくので、<br /> サイトマップも動的に生成するようにしている。</p> <p>Hostingの場合はFunctionsを呼び出せたけれど、<br /> Netlifyに移行すると、それができないので対応が必要。</p> <p><strong>netlifyの_redirectsを使って直接Functionsへリダイレクトする方法で対応した</strong>。</p> <p>こんな感じで各URLを該当のFunctionsにリダイレクトさせておく。</p> <pre><code>/sitemap https://us-central1-<project-id>.cloudfunctions.net/sitemap /* /index.html 200 </code></pre> <h5 id="sitemap-moduleだとダメだった..."><a href="#sitemap-module%E3%81%A0%E3%81%A8%E3%83%80%E3%83%A1%E3%81%A0%E3%81%A3%E3%81%9F...">sitemap-moduleだとダメだった...</a></h5> <p>sitemap-moduleで動的なページの対応方法も乗っているが、<br /> SPAなのでgenerateされてしまう...</p> <ul> <li>参考 <ul> <li><a target="_blank" rel="nofollow noopener" href="https://github.com/nuxt-community/sitemap-module#readme">nuxt-community/sitemap-module: Sitemap Module for Nuxt.js</a></li> <li><a target="_blank" rel="nofollow noopener" href="https://qiita.com/bucchi49/items/d271c4010a3f6c900926">Nuxt.jsで静的ファイル生成時にサイトマップも自動生成する方法 - Qiita</a></li> <li><a target="_blank" rel="nofollow noopener" href="https://qiita.com/cortyuming/items/b4640b371e4ceb37ae47">Nuxt.js + Firestore で動的サイトマップ - Qiita</a></li> <li><a target="_blank" rel="nofollow noopener" href="https://qiita.com/sauzar18/items/2ea958043eb6758c4f83">Nuxt.jsでサイトマップを動的に設定する方法 - Qiita</a></li> </ul></li> </ul> <h4 id="カスタムドメインの設定"><a href="#%E3%82%AB%E3%82%B9%E3%82%BF%E3%83%A0%E3%83%89%E3%83%A1%E3%82%A4%E3%83%B3%E3%81%AE%E8%A8%AD%E5%AE%9A">カスタムドメインの設定</a></h4> <p>Netlifyの設定画面に従い、カスタムドメインを設定していく。</p> <p>ALIASを設定するようにいわれるが、<br /> お名前ドットコムはALIASを設定できないらしいので、<br /> NetlifyのDNSを使う形で対応</p> <p><a target="_blank" rel="nofollow noopener" href="https://qiita.com/hitochan777/items/7acce5b4398af7f4c358">参考にした記事</a>でも書いてあるとおり、<br /> Aレコードでもできるが、CDNを活用できないらしいので同様の対応</p> <p>あとは、設定画面にも書いてあるとおり、<br /> <code>*.netlify.com</code>から設定したドメインへリダイレクトを追加しておく。</p> <pre><code>[https://tsundoku.netlify.com/*](https://tsundoku.netlify.com/*) https://tsundoku.site/:splat 301! /sitemap https://us-central1-<project-id>.cloudfunctions.net/sitemap /* /index.html 200 </code></pre> <p>これでリダイレクトされるようになるので、あとはDNSの反映を待てばOK!</p> <p>以上!!</p> <h2 id="こんなのつくってます!!"><a href="#%E3%81%93%E3%82%93%E3%81%AA%E3%81%AE%E3%81%A4%E3%81%8F%E3%81%A3%E3%81%A6%E3%81%BE%E3%81%99%21%21">こんなのつくってます!!</a></h2> <p>積読用の読書管理アプリ 『積読ハウマッチ』をリリースしました!<br /> <a target="_blank" rel="nofollow noopener" href="https://tsundoku.site">積読ハウマッチ</a>は、Nuxt.js+Firebaseで開発してます!</p> <p><img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/478782/572d4947-f40b-e4dc-1c9c-bc584cd2a66c.png" width="200"/></p> <p>もしよかったら、遊んでみてくださいヽ(=´▽`=)ノ</p> <p>要望・感想・アドバイスなどあれば、<br /> 公式アカウント(<a target="_blank" rel="nofollow noopener" href="https://twitter.com/MemoryLoverz">@MemoryLoverz</a>)や開発者(<a target="_blank" rel="nofollow noopener" href="https://twitter.com/kira_puka">@kira_puka</a>)まで♪</p> <h1 id="参考にしたサイト"><a href="#%E5%8F%82%E8%80%83%E3%81%AB%E3%81%97%E3%81%9F%E3%82%B5%E3%82%A4%E3%83%88">参考にしたサイト</a></h1> <ul> <li><a href="https://crieit.net/posts/Netlify-404">Netlifyを使ってたらルートパス以外が404になった話とその解決方法 - Crieit</a></li> <li><a target="_blank" rel="nofollow noopener" href="https://docs.netlify.com/routing/redirects/rewrites-proxies/#limitations">Rewrites and proxies | Netlify Docs</a></li> <li><a target="_blank" rel="nofollow noopener" href="https://prerender.io/documentation/best-practices">Prerender - Dynamic Rendering for JavaScript Website SEO</a></li> <li><a target="_blank" rel="nofollow noopener" href="https://community.netlify.com/t/common-issue-understanding-and-debugging-prerendering/150">[Common Issue] Understanding and debugging prerendering - Support / Common Issues - Netlify Community</a></li> <li><a target="_blank" rel="nofollow noopener" href="https://qiita.com/NaokiIshimura/items/64e060ccc244e38d0c15#%E3%82%AB%E3%82%B9%E3%82%BF%E3%83%A0%E3%83%89%E3%83%A1%E3%82%A4%E3%83%B3%E3%81%AE%E8%A8%AD%E5%AE%9A">【Netlify】カスタムドメインを設定する - Qiita</a></li> <li><a target="_blank" rel="nofollow noopener" href="https://qiita.com/don-bu-rakko/items/8eb30c7e9a3f9531ba16">Netlifyにお名前.comで取得した独自ドメインを設定する - Qiita</a></li> </ul> きらぷか@積読ハウマッチ/SSSAPIなど tag:crieit.net,2005:PublicArticle/15587 2019-12-08T00:51:56+09:00 2019-12-12T07:36:34+09:00 https://crieit.net/posts/582e915854052994bfba960a9f0f66a4 キャップ野球向けイベントカレンダー作ってみました。【12/12 update】 <p><a href="https://crieit.now.sh/upload_images/3cebc8b53a7a1a70fcf9eb56cb0dfce15debc00300520.jpg" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/3cebc8b53a7a1a70fcf9eb56cb0dfce15debc00300520.jpg?mw=700" alt="" /></a></p> <h1 id="キャップ野球向けイベントカレンダー"><a href="#%E3%82%AD%E3%83%A3%E3%83%83%E3%83%97%E9%87%8E%E7%90%83%E5%90%91%E3%81%91%E3%82%A4%E3%83%99%E3%83%B3%E3%83%88%E3%82%AB%E3%83%AC%E3%83%B3%E3%83%80%E3%83%BC">キャップ野球向けイベントカレンダー</a></h1> <p><a target="_blank" rel="nofollow noopener" href="https://cap-calendar.netlify.com/">https://cap-calendar.netlify.com/</a></p> <p>最近キャップ野球関係のサービスをいくつか作っているのですが、</p> <ul> <li>キャップ野球・全国大会非公式特設サイト</li> <li>野球リーグスコア管理システム ver3α</li> <li>優勝ラインシミュレーター</li> </ul> <p>まだ普及途上にあるマイナースポーツなので、色々広報手段が足りないようで「<a target="_blank" rel="nofollow noopener" href="https://shogi-sanpo.com/">こんなイベントカレンダー</a>欲しい」という声を聞いたので作ってみました。</p> <h1 id="検討した仕組み"><a href="#%E6%A4%9C%E8%A8%8E%E3%81%97%E3%81%9F%E4%BB%95%E7%B5%84%E3%81%BF">検討した仕組み</a></h1> <p>マイブームというわけではないのですが、個人開発ではDBレスのサービスを作ることが多いです。スキーマの設計が面倒なのと個人でデータを持ちたくないのが....。</p> <h2 id="最近作った主なDBレスサービス"><a href="#%E6%9C%80%E8%BF%91%E4%BD%9C%E3%81%A3%E3%81%9F%E4%B8%BB%E3%81%AADB%E3%83%AC%E3%82%B9%E3%82%B5%E3%83%BC%E3%83%93%E3%82%B9">最近作った主なDBレスサービス</a></h2> <ul> <li>キャップ野球・全国大会非公式特設サイト</li> <li>ドラフト風画像作成サービス「ドラフトなう!」</li> <li>アニメランキング作成サービス「Annict Access 3」</li> <li>音楽ランキングメーカー</li> <li>優勝ラインシミュレーター</li> <li>ボウリング幹事アプリ「bowling party manager」</li> </ul> <h2 id="timetree"><a href="#timetree">timetree</a></h2> <p>カレンダー共有ということで一番最初に考慮したのがtimetree。<br /> ただし、<a target="_blank" rel="nofollow noopener" href="https://developers.timetreeapp.com/ja/docs/api">外部公開されているAPI</a>の中にカレンダーに登録されているイベントの一覧を取得するAPIがないことがわかり(19/12/7時点)、採用を断念しました。</p> <h2 id="google calendar API"><a href="#google+calendar+API">google calendar API</a></h2> <p>定番のカレンダー。ただし、現在公開されているv3 APIはOAuth必須となっている。極力ユーザに余計なアクションをさせたくなかったので選択肢から外しました。</p> <h2 id="google public calendar"><a href="#google+public+calendar">google public calendar</a></h2> <p>googleカレンダーには誰でも閲覧できる「公開カレンダー」があり、<br /> その仕組みを調べていたところ、OAuthなしでデータを取得できるという結論に達しました。</p> <h1 id="イベントの種類タグ"><a href="#%E3%82%A4%E3%83%99%E3%83%B3%E3%83%88%E3%81%AE%E7%A8%AE%E9%A1%9E%E3%82%BF%E3%82%B0">イベントの種類タグ</a></h1> <p>イベントの種類を分類するために、イベントのタイトルにタグをつけてもらう方式にしました。<br /> 例:<code>【イベントの種類】【イベントの場所】タイトル</code></p> <h2 id="強敵、正規表現...."><a href="#%E5%BC%B7%E6%95%B5%E3%80%81%E6%AD%A3%E8%A6%8F%E8%A1%A8%E7%8F%BE....">強敵、正規表現....</a></h2> <p>エンジニアとして避けては通れない道なのはわかっているのですが、1つめの【】の中は取得できるものの、繰り返しの取得ができず、</p> <p>(ノ`Д)ノ彡 ┻━┻ ←こんな感じになりました</p> <p>結局文字列を再帰的に検索するという力業で解決しました。<br /> 文字列処理はHSP時代に腐るほどやったんや....</p> <pre><code class="javascript">findTag=(str,tags)=>{ console.log(tags) let beginIdx = str.indexOf('【'); let endIdx = str.indexOf('】'); if(beginIdx !== -1 && endIdx !== -1){ let tagStr = str.substring(beginIdx + 1,endIdx); tags.push(tagStr); this.findTag(str.substring(endIdx + 1,str.length),tags); return tags; }else{ return tags; } } </code></pre> <p>ロースキルでごめんなさい。</p> <h1 id="採用フォント"><a href="#%E6%8E%A1%E7%94%A8%E3%83%95%E3%82%A9%E3%83%B3%E3%83%88">採用フォント</a></h1> <p><a href="https://crieit.now.sh/upload_images/6845d2efb6214962dccd0f411720e2585debc2bd16cfe.png" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/6845d2efb6214962dccd0f411720e2585debc2bd16cfe.png?mw=700" alt="ELLZKugUwAAB9xs.png" /></a></p> <p>普段Noto Sans JPを好んで使っているのですが、Noto Sans JPのままでは若干固いイメージだったので、<code>M PLUS Rounded 1c</code>を使っています。</p> <h1 id="動作イメージ"><a href="#%E5%8B%95%E4%BD%9C%E3%82%A4%E3%83%A1%E3%83%BC%E3%82%B8">動作イメージ</a></h1> <div class="iframe-wrapper"><iframe width="560" height="315" src="https://www.youtube.com/embed/6xYVAQPyqwY" frameborder="0" allow="accelerometer; autoplay; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe></div> <h1 id="未実装の機能"><a href="#%E6%9C%AA%E5%AE%9F%E8%A3%85%E3%81%AE%E6%A9%9F%E8%83%BD">未実装の機能</a></h1> <ul> <li>更新日時順表示</li> </ul> ckoshien tag:crieit.net,2005:PublicArticle/15511 2019-10-27T21:29:52+09:00 2019-10-27T21:29:52+09:00 https://crieit.net/posts/AnnictAccess3 AnnictAccess3をリニューアルしました! <p>進捗的には<a href="https://crieit.net/boards/annict-access/b2e0256fcd24966cd3ace9d0e02910c1">この記事</a>の続きなのですが、大幅にリニューアルを敢行しました。</p> <h1 id="サービスURL"><a href="#%E3%82%B5%E3%83%BC%E3%83%93%E3%82%B9URL">サービスURL</a></h1> <p><a target="_blank" rel="nofollow noopener" href="https://annictaccessv3.netlify.com/">https://annictaccessv3.netlify.com/</a></p> <h1 id="動作イメージ"><a href="#%E5%8B%95%E4%BD%9C%E3%82%A4%E3%83%A1%E3%83%BC%E3%82%B8">動作イメージ</a></h1> <div class="iframe-wrapper"><iframe width="560" height="315" src="https://www.youtube.com/embed/wtPe0MQl7t4" frameborder="0" allow="accelerometer; autoplay; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe></div> <h1 id="今回の機能改善点"><a href="#%E4%BB%8A%E5%9B%9E%E3%81%AE%E6%A9%9F%E8%83%BD%E6%94%B9%E5%96%84%E7%82%B9">今回の機能改善点</a></h1> <h2 id="「アニメ選択」画面の実装"><a href="#%E3%80%8C%E3%82%A2%E3%83%8B%E3%83%A1%E9%81%B8%E6%8A%9E%E3%80%8D%E7%94%BB%E9%9D%A2%E3%81%AE%E5%AE%9F%E8%A3%85">「アニメ選択」画面の実装</a></h2> <p><a href="https://crieit.now.sh/upload_images/f2fa429ca90221dc87d712c2a35832965db586d228738.jpg" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/f2fa429ca90221dc87d712c2a35832965db586d228738.jpg?mw=700" alt="" /></a></p> <ul> <li>ランキングに追加中の番組の強調</li> <li>年を指定して番組一覧を表示</li> </ul> <p>以前のバージョンでは全体から上位30位までを固定で抜き出してそれをソートする形でした。</p> <h2 id="視聴者数の表示"><a href="#%E8%A6%96%E8%81%B4%E8%80%85%E6%95%B0%E3%81%AE%E8%A1%A8%E7%A4%BA">視聴者数の表示</a></h2> <p><a href="https://crieit.now.sh/upload_images/b9eb3a9f22cb008ad6a5a6a394a24e4b5db58362b83ba.jpg" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/b9eb3a9f22cb008ad6a5a6a394a24e4b5db58362b83ba.jpg?mw=700" alt="" /></a></p> <h1 id="今回苦労したこと"><a href="#%E4%BB%8A%E5%9B%9E%E8%8B%A6%E5%8A%B4%E3%81%97%E3%81%9F%E3%81%93%E3%81%A8">今回苦労したこと</a></h1> <h2 id="画像取得に制限がある問題"><a href="#%E7%94%BB%E5%83%8F%E5%8F%96%E5%BE%97%E3%81%AB%E5%88%B6%E9%99%90%E3%81%8C%E3%81%82%E3%82%8B%E5%95%8F%E9%A1%8C">画像取得に制限がある問題</a></h2> <ul> <li>取得数を制限する<br /> →外部APIを探す<br /> →herokuにキャッシュさせる処理を書く</li> </ul> <pre><code class="javascript"> if(isExistFile('./public/'+req.params.url +'.jpg')){ res.sendfile('./public/'+req.params.url +'.jpg'); } try { client.get('users/show',{screen_name: req.params.url}, async(error, tweets, response)=>{ if(error) return Promise.reject(error); console.log(response.statusCode) if(response.statusCode===200){ var json = JSON.parse(response.toJSON().body) let imageRes = await fetch(json.profile_image_url); downloadFile(json.profile_image_url,'./public/'+req.params.url+'.jpg') res.redirect(json.profile_image_url); } }) } catch (error) { console.error(error) } </code></pre> <h1 id="未実装の機能"><a href="#%E6%9C%AA%E5%AE%9F%E8%A3%85%E3%81%AE%E6%A9%9F%E8%83%BD">未実装の機能</a></h1> <ul> <li>映画版ランキングメーカー <ul> <li>the movie db APIからデータを取得する準備中です。</li> <li>日本語/英語両対応するかも?</li> </ul></li> <li>twitterシェア機能</li> </ul> <h1 id="既知の不具合"><a href="#%E6%97%A2%E7%9F%A5%E3%81%AE%E4%B8%8D%E5%85%B7%E5%90%88">既知の不具合</a></h1> <ul> <li>画像が読み込めなかった場合、クリックできる範囲が狭くなる</li> <li>画像取得の際にtwitterAPIの制限に引っ掛かる</li> </ul> <h1 id="今回使っている技術"><a href="#%E4%BB%8A%E5%9B%9E%E4%BD%BF%E3%81%A3%E3%81%A6%E3%81%84%E3%82%8B%E6%8A%80%E8%A1%93">今回使っている技術</a></h1> <h2 id="ReactJS/Netlify"><a href="#ReactJS%2FNetlify">ReactJS/Netlify</a></h2> <p>ReactJS...フロントエンドの画面や通信を司るjavascriptの<code>フレームワーク</code><br /> Netlify...Reactアプリなどを簡単にデプロイできる<code>プラットフォームサービス</code></p> <h2 id="NodeJS/Heroku"><a href="#NodeJS%2FHeroku">NodeJS/Heroku</a></h2> <p>NodeJS...サーバサイドjavascript。<br /> Heroku...NodeJSやRailsなどサーバサイドアプリケーションをデプロイできる<code>プラットフォームサービス</code></p> <h2 id="graphQL"><a href="#graphQL">graphQL</a></h2> <p>サーバにデータを問い合わせる<code>クエリ言語</code>の一つ。<br /> REST APIの後継と言われている。</p> <pre><code class="javascript"> let query = gql`query { searchWorks(orderBy: {field: WATCHERS_COUNT, direction: DESC} seasons: ["${req.params.year}-spring","${req.params.year}-summer","${req.params.year}-autumn","${req.params.year}-winter"]) { edges { node { title seasonName seasonYear annictId twitterUsername watchersCount } } } }` </code></pre> ckoshien tag:crieit.net,2005:PublicArticle/15461 2019-10-08T08:27:14+09:00 2019-10-08T08:27:14+09:00 https://crieit.net/posts/Netlify-Forms お問い合わせフォームを作るならNetlify Formsがオススメ! <p><strong>オリジナルの記事は<a target="_blank" rel="nofollow noopener" href="https://www.mono7555e.com/netlify-forms/">こちら</a></strong></p> <hr /> <p>こんにちは。mono(<a target="_blank" rel="nofollow noopener" href="https://twitter.com/mono7555e" target="_blank" rel="noopener">@mono7555e</a>)です。</p> <p>今回は私のブログのお問い合わせフォームにも使っている<strong>Netlify Forms</strong>のご紹介です。<br /> ブログやサイトにお問い合わせフォームを設置したい時に簡単に使えるのでオススメです。</p> <h2 id="Netlify Formsとは"><a href="#Netlify+Forms%E3%81%A8%E3%81%AF">Netlify Formsとは</a></h2> <p>Netlifyについては以前ご紹介していますのでそちらをご覧ください。</p> <p><a target="_blank" rel="nofollow noopener" href="https://www.mono7555e.com/gatsbyjs-and-netlify/">https://www.mono7555e.com/gatsbyjs-and-netlify/</a></p> <p>そのNetlifyのサービスの1つで「Forms」というフォームの送信内容を受け取ってくれるサービスがあります。</p> <p>利用方法は簡単で、<code><form></code>タグに<code>netlify</code>または<code>data-netlify="true"</code>要素を追加して、Netlifyに反映するだけです。</p> <p>以下、<a target="_blank" rel="nofollow noopener" href="https://www.netlify.com/docs/form-handling/" target="_blank" rel="noopener">公式ドキュメント</a>のサンプルです。</p> <pre><code class="html"><form name="contact" method="POST" data-netlify="true"> <p> <label>Your Name: <input type="text" name="name" /></label> </p> <p> <label>Your Email: <input type="email" name="email" /></label> </p> <p> <label>Your Role: <select name="role[]" multiple> <option value="leader">Leader</option> <option value="follower">Follower</option> </select></label> </p> <p> <label>Message: <textarea name="message"></textarea></label> </p> <p> <button type="submit">Send</button> </p> </form> </code></pre> <p><code><form></code>タグの<code>data-netlify="true"</code>以外は普通のフォームなのが分かると思います。</p> <p><code><form></code>タグに<code>data-netlify="true"</code>をつける以外に制約はないので、フォームの見た目などのカスタマイズは自由です。</p> <h3 id="価格"><a href="#%E4%BE%A1%E6%A0%BC">価格</a></h3> <p>月に<strong>100件</strong>、アップロードデータサイズ<strong>10MB</strong>までは<strong>無料</strong>で使えます。</p> <p>それ以上になると、19ドル/月の費用がかかります。詳しくは<a target="_blank" rel="nofollow noopener" href="https://www.netlify.com/pricing/" target="_blank" rel="noopener">公式サイト</a>をご覧ください。</p> <h3 id="主な機能"><a href="#%E4%B8%BB%E3%81%AA%E6%A9%9F%E8%83%BD">主な機能</a></h3> <h4 id="ファイル添付"><a href="#%E3%83%95%E3%82%A1%E3%82%A4%E3%83%AB%E6%B7%BB%E4%BB%98">ファイル添付</a></h4> <p><code>type="file"</code>のフィールドを追加すれば、それだけでファイルもアップロードできるようになります。</p> <pre><code class="html"><form name="contact" method="post" data-netlify="true"> ... <input type="file" name="screenshot" /> ... </form> </code></pre> <h4 id="スパムフィルタリング"><a href="#%E3%82%B9%E3%83%91%E3%83%A0%E3%83%95%E3%82%A3%E3%83%AB%E3%82%BF%E3%83%AA%E3%83%B3%E3%82%B0">スパムフィルタリング</a></h4> <p>送信された内容はAkismetを使ってフィルタリングされます。</p> <p>フィルタリングされたものはダッシュボード画面から確認出来るので、誤ってスパム判定されて確認できないということはないです。</p> <p>メールアドレスの形式が間違っている場合などもフィルタリングされて弾かれるみたいです。</p> <p>さらに、「reCAPTCHA 2」などを使ってフォームを保護することができるので必要であれば使ってみると良さそうです。</p> <p>詳しくは<a target="_blank" rel="nofollow noopener" href="https://www.netlify.com/docs/form-handling/" target="_blank" rel="noopener">公式ドキュメント</a>をご覧ください。</p> <h4 id="エクスポート"><a href="#%E3%82%A8%E3%82%AF%E3%82%B9%E3%83%9D%E3%83%BC%E3%83%88">エクスポート</a></h4> <p>送られてきた内容は基本的にはNetlifyのダッシュボード画面から確認することになりますが、CSV形式でのエクスポート機能もあります。</p> <p>ダウンロードできるのはスパム判定されていないもののみです。</p> <h4 id="通知"><a href="#%E9%80%9A%E7%9F%A5">通知</a></h4> <p>メールやSlackで通知を飛ばすことができます。</p> <p><code>name="subject"</code>の要素をフォームに追加しておけば、通知のタイトルに入力された内容が使われるようになるのでオススメです。</p> <pre><code class="html"><form name="contact" method="post" data-netlify="true"> ... <input type="input" name="subject" /> ... </form> </code></pre> <p>こちらもエクスポートと同様、スパム判定されていない場合のみ通知されます。</p> <h4 id="サンクスページ"><a href="#%E3%82%B5%E3%83%B3%E3%82%AF%E3%82%B9%E3%83%9A%E3%83%BC%E3%82%B8">サンクスページ</a></h4> <p>サンクスページを用意して<code><form></code>タグの<code>action</code>にサンクスページのパスを設定するだけでOKです。</p> <pre><code class="html"><form name="contact" method="post" action="/success.html" data-netlify="true"> ... <input type="input" name="subject" /> ... </form> </code></pre> <p>何も設定していない場合はNetlifyが用意した画面が表示されます。</p> <p>カスタマイズ等はできないので自前で用意する方が良いでしょう。</p> <h2 id="GatsbyJSで利用する場合の注意点"><a href="#GatsbyJS%E3%81%A7%E5%88%A9%E7%94%A8%E3%81%99%E3%82%8B%E5%A0%B4%E5%90%88%E3%81%AE%E6%B3%A8%E6%84%8F%E7%82%B9">GatsbyJSで利用する場合の注意点</a></h2> <p>GatsbyJSやその他静的サイトジェネレーターからも利用可能ですが、サンプルのままだと動かないので一部コードの追加が必要になります。</p> <p>以下のようなコードを<code><form></code>に追加します。</p> <pre><code class="jsx"><input type="hidden" name="form-name" value="contact" /> </code></pre> <p>valueの内容は<code><form></code>タグの<code>name</code>属性に合わせて変更してください。</p> <h4 id="サンプル"><a href="#%E3%82%B5%E3%83%B3%E3%83%97%E3%83%AB">サンプル</a></h4> <pre><code class="jsx"><form name="contact" method="post" data-netlify="true" data-netlify-honeypot="bot-field"> {/* 以下のコードを追加 */} <input type="hidden" name="form-name" value="contact" /> ... </form> </code></pre> <h2 id="Netlify Forms以外の選択肢"><a href="#Netlify+Forms%E4%BB%A5%E5%A4%96%E3%81%AE%E9%81%B8%E6%8A%9E%E8%82%A2">Netlify Forms以外の選択肢</a></h2> <p>このブログがGatsbyJS+Netlifyという構成で作られていたのでNetlify Formsを使ってお問い合わせフォームを構築しましたが、Netlifyを使っていない場合や難しく感じた場合は<a target="_blank" rel="nofollow noopener" href="https://www.google.com/intl/ja_jp/forms/about/" target="_blank" rel="noopener">Googleフォーム</a>を使うのが良いと思います。</p> <h2 id="まとめ"><a href="#%E3%81%BE%E3%81%A8%E3%82%81">まとめ</a></h2> <p>以前からTwitterのDMにてご依頼等いただくことがありましたが、企業さまだと少々お手間だったようなので、お問い合わせフォームを設置することにしました。</p> <p>Netlify Formsなら簡単に設置できますし、スパムも自動で判別してくれるので管理の手間も少なくオススメです。</p> <h2 id="参考"><a href="#%E5%8F%82%E8%80%83">参考</a></h2> <h3 id="公式ドキュメント"><a href="#%E5%85%AC%E5%BC%8F%E3%83%89%E3%82%AD%E3%83%A5%E3%83%A1%E3%83%B3%E3%83%88">公式ドキュメント</a></h3> <p><a target="_blank" rel="nofollow noopener" href="https://www.netlify.com/docs/form-handling/">https://www.netlify.com/docs/form-handling/</a></p> <h3 id="ReactアプリにNetlify Formsを入れる方法(公式ブログ)"><a href="#React%E3%82%A2%E3%83%97%E3%83%AA%E3%81%ABNetlify+Forms%E3%82%92%E5%85%A5%E3%82%8C%E3%82%8B%E6%96%B9%E6%B3%95%EF%BC%88%E5%85%AC%E5%BC%8F%E3%83%96%E3%83%AD%E3%82%B0%EF%BC%89">ReactアプリにNetlify Formsを入れる方法(公式ブログ)</a></h3> <p><a target="_blank" rel="nofollow noopener" href="https://www.netlify.com/blog/2017/07/20/how-to-integrate-netlifys-form-handling-in-a-react-app/">https://www.netlify.com/blog/2017/07/20/how-to-integrate-netlifys-form-handling-in-a-react-app/</a></p> mono7555e tag:crieit.net,2005:PublicArticle/15460 2019-10-08T08:07:21+09:00 2019-10-08T08:11:32+09:00 https://crieit.net/posts/GatsbyJS-Netlify-5d9bc529145a3 いまブログを始めるならGatsbyJS+Netlifyがオススメ! <p><strong>オリジナルの記事は<a target="_blank" rel="nofollow noopener" href="https://www.mono7555e.com/gatsbyjs-and-netlify/">こちら</a></strong></p> <hr /> <p>こんにちは。mono(<a target="_blank" rel="nofollow noopener" href="https://twitter.com/mono7555e" target="_blank" rel="noopener">@mono7555e</a>)です。</p> <p>今回は新しくブログを立ち上げた際に使ったGatsbyJSとNetlifyのご紹介です。<br /> 新しくブログを立ち上げようとしている方やWordPressに疲れた方には参考になるんじゃないかと思います。</p> <h2 id="GatsbyJSとは"><a href="#GatsbyJS%E3%81%A8%E3%81%AF">GatsbyJSとは</a></h2> <p><a target="_blank" rel="nofollow noopener" href="https://www.gatsbyjs.org/">https://www.gatsbyjs.org/</a></p> <p>GatsbyJSは<a target="_blank" rel="nofollow noopener" href="https://reactjs.org/" target="_blank" rel="noopener">React</a>で作られた静的サイトジェネレーターです。<br /> JSONやYAML、XMLなど扱うデータを全て一回GraphQLで取り出せるように変換してくれるので、様々なデータが扱いやすく、コンポーネント化がしやすいという特長があります。</p> <p>また、プラグインも豊富で大抵の場合、既にプラグインがあるのでそれを使うように設定してあげればOKです。</p> <p>今回のブログ立ち上げの際にも<strong>Markdownで記事が書ける</strong>というのを必須事項にあげていたので<code>gatsby-transformer-remark</code>というプラグインを利用してMakrdownファイルをGraphQLで扱えるようにしました。</p> <p>他にもsitemap.xmlを生成してくれる<code>gatsby-plugin-sitemap</code>、Google Tag Managerのタグを入れてくれる<code>gatsby-plugin-google-tagmanager</code>など利用しています。</p> <h2 id="なぜWordPressを使わなかったのか"><a href="#%E3%81%AA%E3%81%9CWordPress%E3%82%92%E4%BD%BF%E3%82%8F%E3%81%AA%E3%81%8B%E3%81%A3%E3%81%9F%E3%81%AE%E3%81%8B">なぜWordPressを使わなかったのか</a></h2> <p>ブログを立ち上げるとなると真っ先に思い浮かぶのがWordPressだと思います。<br /> ですが、今回は使うのをやめました。<br /> WordPressを使った経験はある方なら分かると思うのですが色々つらみを感じているからです。</p> <h3 id="セキュリティが不安"><a href="#%E3%82%BB%E3%82%AD%E3%83%A5%E3%83%AA%E3%83%86%E3%82%A3%E3%81%8C%E4%B8%8D%E5%AE%89">セキュリティが不安</a></h3> <p>WordPressはかなり使われているので様々な方面から狙われやすいです。<br /> セキュリティパッチ等のアップデートを忘れているとすぐに狙われて乗っ取られたり、悪意のあるコードを埋め込められたりする恐れがあります。</p> <h3 id="管理が大変"><a href="#%E7%AE%A1%E7%90%86%E3%81%8C%E5%A4%A7%E5%A4%89">管理が大変</a></h3> <p>前述の通りセキュリティが不安なのでセキュリティパッチが降ってきたらすぐに適用したいのですがカスタマイズをバリバリやっていたりプラグインを使っている場合、それらが邪魔をしてアップデートが出来ないことがままあります。<br /> また、WordPressだけでなくPHPやWebサーバーの管理等も必要なのでその辺りも大変です(レンタルサーバーの場合はその辺り任せられるので楽ですが、カスタマイズがしづらい場合もあります。)</p> <h3 id="PHP書くの辛い"><a href="#PHP%E6%9B%B8%E3%81%8F%E3%81%AE%E8%BE%9B%E3%81%84">PHP書くの辛い</a></h3> <p>最後はかなり個人的な理由です。<br /> WordPressはPHPで作られているので、ある程度カスタマイズをしようと思うとPHPでコードを書く必要があるのですが、PHPは書いててあまり楽しい言語ではないので出来れば避けたいと思っています。</p> <h2 id="なぜブログサービスを使わなかったのか"><a href="#%E3%81%AA%E3%81%9C%E3%83%96%E3%83%AD%E3%82%B0%E3%82%B5%E3%83%BC%E3%83%93%E3%82%B9%E3%82%92%E4%BD%BF%E3%82%8F%E3%81%AA%E3%81%8B%E3%81%A3%E3%81%9F%E3%81%AE%E3%81%8B">なぜブログサービスを使わなかったのか</a></h2> <p>WordPress以外にもブログをはじめるのに使えるサービスがいくつかありますが、Markdown、アフィリエイト、カスタマイズ性の辺りがマッチせず使うのを諦めました。</p> <p>有名なところだとAmebaブログ、ライブドアブログ、fc2ブログなどがありますが、それらのブログサービスはMarkdownで記事が書けないので除外しました。</p> <p>Markdownが使えるのは、調べた限りだとMedium、note、Qiita、はてなブログぐらいですが、それらも他の部分がネックになりました。</p> <h3 id="Medium"><a href="#Medium">Medium</a></h3> <p>使ったことがないので何とも言えないところですが、日本で使っている人が少ない印象で、検索に引っかかることもないのでSEOにも強くなさそうな印象です。<br /> <a target="_blank" rel="nofollow noopener" href="https://medium.com/@MEJapan/from-medium-japan-ad346bee2a9b">2017年に日本から撤退</a>しているのも気になりました。</p> <h3 id="note"><a href="#note">note</a></h3> <p>アフィリエイトが貼れなかったり、カスタマイズも出来ないのがちょっと辛いかなと。<br /> ただ、有料記事が販売できたりするのでブログとは別に利用しようと思っています。</p> <p><a target="_blank" rel="nofollow noopener" href="https://note.mu/mono7555e">https://note.mu/mono7555e</a></p> <h3 id="Qiita"><a href="#Qiita">Qiita</a></h3> <p>noteと同様にアフィリエイト、カスタマイズが出来ないのと、プログラミングに関するものしか投稿できないというのがネックです。<br /> もしかしたらめちゃくちゃコードを見せるような記事はQiitaに書くかもしれません。</p> <p>一応登録だけはしました。</p> <p><a target="_blank" rel="nofollow noopener" href="https://qiita.com/mono7555e">https://qiita.com/mono7555e</a></p> <h3 id="はてなブログ"><a href="#%E3%81%AF%E3%81%A6%E3%81%AA%E3%83%96%E3%83%AD%E3%82%B0">はてなブログ</a></h3> <p>4つの中だと、<a target="_blank" rel="nofollow noopener" href="https://hatenablog.com/" target="_blank" rel="noopener">はてなブログ</a>は結構良くてサクッと立ち上げるならオススメです。<br /> 独自ドメインを設定する場合は有料プランになってしまいますが気にしない方なら無料版で事足りると思います。<br /> 今回はガッツリカスタマイズしたかったのでやめました。</p> <h2 id="GatsbyJSを選んだ理由"><a href="#GatsbyJS%E3%82%92%E9%81%B8%E3%82%93%E3%81%A0%E7%90%86%E7%94%B1">GatsbyJSを選んだ理由</a></h2> <p>ということでGatsbyJSです。</p> <p>GatsbyJSはReactベースでGraphQLでデータを扱うというのが当時、流行っていたというのもあって採用することにしました。</p> <h2 id="GatsbyJSの良いところ、悪いところ"><a href="#GatsbyJS%E3%81%AE%E8%89%AF%E3%81%84%E3%81%A8%E3%81%93%E3%82%8D%E3%80%81%E6%82%AA%E3%81%84%E3%81%A8%E3%81%93%E3%82%8D">GatsbyJSの良いところ、悪いところ</a></h2> <h3 id="高速"><a href="#%E9%AB%98%E9%80%9F">高速</a></h3> <p>WordPressなどと違い事前にHTMLを生成してあるのでレスポンスが高速です。</p> <h3 id="維持費が安い"><a href="#%E7%B6%AD%E6%8C%81%E8%B2%BB%E3%81%8C%E5%AE%89%E3%81%84">維持費が安い</a></h3> <p>基本的には静的HTMLを置いておくだけでOKなので、AppサーバーやDBは不要。<br /> 後述するNetlify等の静的サイトをホスティングするサービスを使うだけでほとんどの場合事足ります。</p> <h3 id="豊富なプラグイン、ライブラリ"><a href="#%E8%B1%8A%E5%AF%8C%E3%81%AA%E3%83%97%E3%83%A9%E3%82%B0%E3%82%A4%E3%83%B3%E3%80%81%E3%83%A9%E3%82%A4%E3%83%96%E3%83%A9%E3%83%AA">豊富なプラグイン、ライブラリ</a></h3> <p>公式、非公式に関わらずプラグインが多数あるのと、npmも使えます。<br /> それらの組み合わせで作ることが出来ます。</p> <h3 id="専門知識が必要"><a href="#%E5%B0%82%E9%96%80%E7%9F%A5%E8%AD%98%E3%81%8C%E5%BF%85%E8%A6%81">専門知識が必要</a></h3> <p>一方で、ReactやGraphQL、ES6等の知識が必要です。<br /> フロントエンドエンジニアの方であれば問題ないと思いますが、そうでない場合は少しキャッチアップが必要かもしれません。</p> <p>GatsbyJSを採用したのでホスティングを考えなければなりませんが、GatsbyJSを使うならNetlifyを使うというのがデファクトスタンダードになっています。<br /> なので私も例に漏れずNetlifyを使うことにしました。</p> <h2 id="Netlifyとは"><a href="#Netlify%E3%81%A8%E3%81%AF">Netlifyとは</a></h2> <p><a target="_blank" rel="nofollow noopener" href="https://www.netlify.com/">https://www.netlify.com/</a></p> <p>静的サイトをホスティングしてくれるサービスです。<br /> GitHubやBitbucketのリポジトリと連携するだけで、pushを検知して自動でビルド、デプロイしてくれます。</p> <h2 id="Netlifyの良いところ"><a href="#Netlify%E3%81%AE%E8%89%AF%E3%81%84%E3%81%A8%E3%81%93%E3%82%8D">Netlifyの良いところ</a></h2> <h3 id="無料で独自ドメインを設定できる"><a href="#%E7%84%A1%E6%96%99%E3%81%A7%E7%8B%AC%E8%87%AA%E3%83%89%E3%83%A1%E3%82%A4%E3%83%B3%E3%82%92%E8%A8%AD%E5%AE%9A%E3%81%A7%E3%81%8D%E3%82%8B">無料で独自ドメインを設定できる</a></h3> <p>基本的には<code>.netlify.com</code>のサブドメインが設定されますが、独自ドメインを持っていればそれを無料で設定できます。</p> <h3 id="無料でHTTPSにできる"><a href="#%E7%84%A1%E6%96%99%E3%81%A7HTTPS%E3%81%AB%E3%81%A7%E3%81%8D%E3%82%8B">無料でHTTPSにできる</a></h3> <p><code>.netlify.com</code>のサブドメインであっても、独自ドメインであってもHTTPSにするのは無料で出来ます。</p> <h3 id="デプロイ前にプレビューできる"><a href="#%E3%83%87%E3%83%97%E3%83%AD%E3%82%A4%E5%89%8D%E3%81%AB%E3%83%97%E3%83%AC%E3%83%93%E3%83%A5%E3%83%BC%E3%81%A7%E3%81%8D%E3%82%8B">デプロイ前にプレビューできる</a></h3> <p>ブランチを切ると自動的にプレビュー用のサイトが自動で作られるので変更内容を公開前に確認できます。</p> <p>悪いところも書こうと思ったんですが思いつかないですね…<br /> 一応、無料だと100GBまでの転送量の制限がありますが、多くても数100MBぐらいしか使わないと思うので全然問題ないかなと思います。</p> <h2 id="まとめ"><a href="#%E3%81%BE%E3%81%A8%E3%82%81">まとめ</a></h2> <p>私はブログを立ち上げるときの条件</p> <ul> <li>WordPress以外</li> <li>独自ドメインが使える</li> <li>Markdownで書ける</li> <li>カスタマイズしやすい</li> </ul> <p>これらを満たすのがGatsbyJSとNetlifyの組み合わせだったので使用しています。</p> <p>同じような条件で検討されている方はGatsbyJSとNetlifyの組み合わせがオススメなので是非試してみてください。</p> mono7555e tag:crieit.net,2005:PublicArticle/14948 2019-04-24T19:15:13+09:00 2019-04-24T19:15:13+09:00 https://crieit.net/posts/Netlify-submodule-clone Netlifyでsubmoduleのcloneに失敗する時 <pre><code class="sh">12:04:27 AM: Error checking out submodules: Submodule 'src/lib/project' ([email protected]:owner/project.git) registered for path 'src/lib/project' Cloning into '/opt/build/repo/src/lib/project'... Host key verification failed. fatal: Could not read from remote repository. Please make sure you have the correct access rights and the repository exists. fatal: clone of '[email protected]:owner/project.git' into submodule path '/opt/build/repo/src/lib/project' failed Failed to clone 'src/lib/project'. Retry scheduled Cloning into '/opt/build/repo/src/lib/general'... Host key verification failed. </code></pre> <p>git submoduleを使っていて、Netlify上でこんなエラーが出た時は、下記の様にするとうまくいく。</p> <p><code>diff:.gitmodules [submodule "src/lib/project"] path = src/lib/project - url = [email protected]:owner/project.git + url = git://github.com/owner/project.git</code></p> <p>以前にも遭遇して、そのときに何かを参考にして解決したんだと思うけど、忘れちゃいました。公式ドキュメントだったかな?次回すぐに当たれるようにメモ。</p> naokazuterada tag:crieit.net,2005:PublicArticle/14940 2019-04-21T22:24:31+09:00 2019-04-24T12:19:15+09:00 https://crieit.net/posts/URL-netlify-Large-Media URLパラメータで簡単画像リサイズ。netlify の Large Media が便利! <h1 id="netlify の Large Media とは"><a href="#netlify+%E3%81%AE+Large+Media+%E3%81%A8%E3%81%AF">netlify の Large Media とは</a></h1> <p>Git レポジトリでサイトコンテンツを管理する際に、画像やPSD/ZIPなどの大きいファイルを含めて管理するために Git LFS(Git Large File Strage)と仕組みがある。<br /> LFS については細かく触れないが、LFS を使って netlify にデプロイされた画像を、URLパラメータをつかってリサイズやクロップが便利に扱えるように使える神機能である。</p> <p>Large Media 公式ドキュメント<br /> <a target="_blank" rel="nofollow noopener" href="https://www.netlify.com/docs/large-media/#enabling-netlify-large-media">https://www.netlify.com/docs/large-media/#enabling-netlify-large-media</a></p> <p>Git LFS 公式サイト<br /> <a target="_blank" rel="nofollow noopener" href="https://git-lfs.github.com/">https://git-lfs.github.com/</a></p> <h2 id="netlify Large Media のサンプル"><a href="#netlify+Large+Media+%E3%81%AE%E3%82%B5%E3%83%B3%E3%83%97%E3%83%AB">netlify Large Media のサンプル</a></h2> <p>例えば、普通に gitレポジトリ に含めて github などを介して netlify デプロイすると、このようなURLで画像にアクセスできる。</p> <p><a target="_blank" rel="nofollow noopener" href="https://netlify-photo-gallery.netlify.com/images/apple.jpg">https://netlify-photo-gallery.netlify.com/images/apple.jpg</a><br /> <img src="https://netlify-photo-gallery.netlify.com/images/apple.jpg?nf_resize=fit&w=1000&h=1000" alt="りんご画像" /></p> <p>これにこんな形で、最後に <code>?nf_resize=fit&w=200</code> を加えてやると、リサイズされた画像を生成して返してくれるのだ。<br /> <a target="_blank" rel="nofollow noopener" href="https://netlify-photo-gallery.netlify.com/images/apple.jpg?nf_resize=fit&w=200">https://netlify-photo-gallery.netlify.com/images/apple.jpg?nf_resize=fit&w=200</a><br /> <img src="https://netlify-photo-gallery.netlify.com/images/apple.jpg?nf_resize=fit&w=200" alt="リサイズされたりんご画像" /></p> <p>さらに次は、画像URLにの最後に <code>?nf_resize=smartcrop&w=200</code> を加えてやると、クロップされた画像を生成して返してくれる。</p> <p><img src="https://netlify-photo-gallery.netlify.com/images/apple.jpg?nf_resize=smartcrop&w=200&h=200" alt="クロップされたりんご画像" /></p> <p>1つ画像をアップするだけでいい感じにリサイズされた画像やクロップされたをすぐに使えるのだ!神すぎないか?</p> <p>これが <strong>netlify Large Media</strong> 機能だ。</p> <p>公式サンプルに Transformation demo とジェネレーターがあるので、もう少し触ってみたいかたはこちら。<br /> <a target="_blank" rel="nofollow noopener" href="https://netlify-photo-gallery.netlify.com">https://netlify-photo-gallery.netlify.com</a></p> <h2 id="netlify Large Media の使い方"><a href="#netlify+Large+Media+%E3%81%AE%E4%BD%BF%E3%81%84%E6%96%B9">netlify Large Media の使い方</a></h2> <h3 id="環境準備"><a href="#%E7%92%B0%E5%A2%83%E6%BA%96%E5%82%99">環境準備</a></h3> <p>最初に一度ローカル環境を準備する必要がある。</p> <h4 id="バージョン確認"><a href="#%E3%83%90%E3%83%BC%E3%82%B8%E3%83%A7%E3%83%B3%E7%A2%BA%E8%AA%8D">バージョン確認</a></h4> <p><code>git lfs version</code><br /> Git LFS version: v2.5.1以上 を確認<br /> なければ、Git LFS をインストール。詳細は公式サイトで割愛<br /> <a target="_blank" rel="nofollow noopener" href="https://git-lfs.github.com/">https://git-lfs.github.com/</a></p> <p><code>netlify -v</code><br /> netlify-cli: 2.6.4以上 を確認</p> <p>なければ、netlify-cli をインストール。詳細は公式ドキュメントで割愛<br /> <a target="_blank" rel="nofollow noopener" href="https://www.netlify.com/docs/cli/">https://www.netlify.com/docs/cli/</a></p> <h4 id="large media プラグインインストール"><a href="#large+media+%E3%83%97%E3%83%A9%E3%82%B0%E3%82%A4%E3%83%B3%E3%82%A4%E3%83%B3%E3%82%B9%E3%83%88%E3%83%BC%E3%83%AB">large media プラグインインストール</a></h4> <p>バージョン確認後、netlify-cliのプラグインをインストール</p> <pre><code>netlify plugins:install netlify-lm-plugin netlify lm:install </code></pre> <p>これで環境準備は完了</p> <h3 id="サイト(レポジトリ)ごとの設定"><a href="#%E3%82%B5%E3%82%A4%E3%83%88%EF%BC%88%E3%83%AC%E3%83%9D%E3%82%B8%E3%83%88%E3%83%AA%EF%BC%89%E3%81%94%E3%81%A8%E3%81%AE%E8%A8%AD%E5%AE%9A">サイト(レポジトリ)ごとの設定</a></h3> <p>Large Media 機能を使うには、サイトごとに機能を ON にしないといけないので、サイトごとで設定。<br /> 1. まずは、netlifyでプロジェクトを作成 と github レポジトリが紐付いていることを確認<br /> 2. その後、ローカルのレポジトリも紐付ける</p> <pre><code>netlify link </code></pre> <p>3.セットアップコマンドをつ</p> <pre><code>netlify lm:setup </code></pre> <p>4.セットアップコマンドをつ<br /> <code>git status</code> で、<code>.lfsconfig</code> が生成されていることを確認し、git add で<code>.lfsconfig</code>を git の管理下におく。</p> <h3 id="LFS で管理する"><a href="#LFS+%E3%81%A7%E7%AE%A1%E7%90%86%E3%81%99%E3%82%8B">LFS で管理する</a></h3> <p>サイトごとの設定の他に、LFS でファイルを管理する必要があるので、</p> <p>画像ごとでトラッキングしていくか、gitignore の <a target="_blank" rel="nofollow noopener" href="https://git-scm.com/docs/gitignore#_pattern_format">pattern rules</a> も使えるので、LFSで管理するように指定していく。</p> <pre><code>git lfs track "sample.jpeg" git lfs track "*.jpeg" </code></pre> <p>トラッキングされたファイル一覧はこちらで確認可能。</p> <pre><code>git lfs track </code></pre> <p>トラッキング時には <code>.gitattributes</code> が生成されるので、こちらも git add と commit して管理する。</p> <p>詳しくは netlify のドキュメント、または、Git LFS 公式サイトで。<br /> <a target="_blank" rel="nofollow noopener" href="https://www.netlify.com/docs/large-media/#large-media-file-tracking-configuration">https://www.netlify.com/docs/large-media/#large-media-file-tracking-configuration</a></p> <h3 id="確認"><a href="#%E7%A2%BA%E8%AA%8D">確認</a></h3> <p>うまくnetlify large media の設定と LFS で設定できていれば、あとはいつもどおりレポジトリに push をするだけで、large media 機能がつかえるようになる。</p> <p><a target="_blank" rel="nofollow noopener" href="https://lfs-test.netlify.com/sample.jpeg?nf_resize=smartcrop&w=100&h=200">https://lfs-test.netlify.com/sample.jpeg?nf_resize=smartcrop&w=100&h=200</a><br /> <img src="https://lfs-test.netlify.com/sample.jpeg?nf_resize=smartcrop&w=100&h=200" alt="木" /><br /> 最初のサンプルのようにパラメーターをつけるだけで、Large Media が使えるようになっている!</p> <p>正常に動いていれば、netlify 管理画面もこんな感じに、ドキュメントのリンクの画面から管理している画像の一覧に変わります。</p> <h3><img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/6531/1c50d7f0-e372-775d-df7d-379669687ba4.png" alt="Screen Shot on 0031-04-21 at 21:41:59.png" /></h3> <p>公式のサンプルもあるけど、自分でためしたサンプルも一応<br /> <a target="_blank" rel="nofollow noopener" href="https://github.com/yahsan2/lfs-test">https://github.com/yahsan2/lfs-test</a></p> <h2 id="netlify Large Media と CMS"><a href="#netlify+Large+Media+%E3%81%A8+CMS">netlify Large Media と CMS</a></h2> <p>この netlify large media 機能は、 netlify CMS をつかって画像をアップロードする lfs として画像をコミットしてくれるので、この機能を簡単につかえるので、netlifyCMS もおすすめですね。つまり画像リサイズをもった JAMstack な CMS も簡単につくれるわけです。<br /> <a target="_blank" rel="nofollow noopener" href="https://www.netlifycms.org/docs/netlify-large-media/">https://www.netlifycms.org/docs/netlify-large-media/</a></p> <p>なんか静的サイトホスティングサービスでシンプルなのに、このかゆいところにどんどん手が届いてく netlify 拡張具合ほんとファンです。</p> yahsan2 tag:crieit.net,2005:PublicArticle/14775 2019-01-31T23:57:42+09:00 2019-01-31T23:57:42+09:00 https://crieit.net/posts/Netlify-functions Netlifyでfunctionsのデプロイがなぜか失敗する時の対処法 <p>Netlifyでfunctionsを含めたプロジェクトをデプロイしようとすると、エラーでデプロイできなかった。不思議に思いながら本家のfunctionsのサンプルを「Deploy to Netlify」ボタンでデプロイしてみたのだが、それもデプロイできない。ちなみに発生したエラーは下記のようなもの。</p> <pre><code>failed during stage 'deploying site': Failed to execute deploy: [PUT /deploys/{deploy_id}/functions/{name}][500] uploadDeployFunction default &{Code:0 Message:} </code></pre> <h2 id="対処法"><a href="#%E5%AF%BE%E5%87%A6%E6%B3%95">対処法</a></h2> <p>待とう。何かNetlifyの機嫌が悪いだけかもしれない。もしくはアカウントやプロジェクトを作ったばかりの時はLambda側の連携の準備ができていないのかもしれない。</p> <p>僕の場合は何時間かたって再度Retryしてみたらデプロイができた。</p> <p>ただ、元々成功していたプロジェクトのデプロイが急にできなくなったのであれば、違う原因かもしれないので注意。エラーメッセージを要確認。</p> だら@Crieit開発者 tag:crieit.net,2005:PublicArticle/14749 2019-01-26T19:42:57+09:00 2019-05-12T08:46:35+09:00 https://crieit.net/posts/ReactJS-Netlify-OGP ReactJS+NetlifyでOGP対応 <h1 id="OGPとは"><a href="#OGP%E3%81%A8%E3%81%AF">OGPとは</a></h1> <p>Open Graph Protocolの略。<br /> FacebookやtwitterなどのURLシェア機能で画像と文字列を使ってWEBサイトの内容を伝えるための規格です。<br /> <a href="https://crieit.now.sh/upload_images/b08b548caec5795ad40e35af9c85f4c95c4c309f9d5b0.png" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/b08b548caec5795ad40e35af9c85f4c95c4c309f9d5b0.png?mw=700" alt="image" /></a></p> <h1 id="ReactでOGPが難しい理由"><a href="#React%E3%81%A7OGP%E3%81%8C%E9%9B%A3%E3%81%97%E3%81%84%E7%90%86%E7%94%B1">ReactでOGPが難しい理由</a></h1> <p>FacebookやtwitterなどのクローラーがJavascriptを実行しない仕様のため、ページの内容がJavascriptで動的に作成されるSPAなどでは工夫が必要となります。</p> <h1><a target="_blank" rel="nofollow noopener" href="https://github.com/nfl/react-helmet">react-helmet</a>を使ってmetaタグを動的に書き換える</h1> <p><a target="_blank" rel="nofollow noopener" href="https://github.com/nfl/react-helmet">react-helmet</a><br /> metaタグだけではなく、headタグの中身を動的に書き換えることができます。(reduxの)<strong>store</strong>から文字列をセットしたりできます。</p> <pre><code class="javascript"><Helmet title={store.getState().data.league.title} meta={[ { property:"og:image", content:"https://jcblscore-react.netlify.com/img/ogp.png"}, { property:"og:url", content:location}, { property:"og:type", content:"article"}, { property:"og:title", content:store.getState().data.league.title}, { property:"og:description", content:description}, { property:"og:site_name", content:"野球リーグスコア管理システム"}, { name:"twitter:card",content:"summary_large_image"}, { name:"fragment",content:"!"} ]} /> </code></pre> <pre><code class="javascript">componentWillMount(){ window.prerenderReady = false; } componentDidMount(){ window.prerenderReady = true; } </code></pre> <h1 id="NetlifyでPrerenderingの設定をする"><a href="#Netlify%E3%81%A7Prerendering%E3%81%AE%E8%A8%AD%E5%AE%9A%E3%82%92%E3%81%99%E3%82%8B">NetlifyでPrerenderingの設定をする</a></h1> <p>Settings -> Build&Deploy -> Prerendering から下図のようにEnabledにする<br /> <a href="https://crieit.now.sh/upload_images/e40e06bce35e3ceca1be5b957251e7805c4c369da0756.png" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/e40e06bce35e3ceca1be5b957251e7805c4c369da0756.png?mw=700" alt="image" /></a></p> <h1 id="おまけ"><a href="#%E3%81%8A%E3%81%BE%E3%81%91">おまけ</a></h1> <p>crieitさんのボード機能を使って<a href="https://crieit.net/boards/baseball-score-management">野球リーグスコア管理システムの進捗</a>とか書いてます。選手個人成績のページもOGPに対応しました。<br /> <a href="https://crieit.now.sh/upload_images/7f39cb0c5dca52ee81244c660d3c2ff65c4c30b291cbb.png" target="_blank" rel="nofollow noopener"><img src="https://crieit.now.sh/upload_images/7f39cb0c5dca52ee81244c660d3c2ff65c4c30b291cbb.png?mw=700" alt="image" /></a></p> <h1 id="参考リンク"><a href="#%E5%8F%82%E8%80%83%E3%83%AA%E3%83%B3%E3%82%AF">参考リンク</a></h1> <ul> <li><a target="_blank" rel="nofollow noopener" href="https://qiita.com/kiida/items/07e2c6ccb9e3ea230b16">SSR無しでReactアプリをOGPとかに対応させる (自力prerendering編)</a></li> <li><a target="_blank" rel="nofollow noopener" href="https://akameco.github.io/blog/react-helmet/">React Helmetを使ってOGP対応した</a></li> <li><a target="_blank" rel="nofollow noopener" href="https://qiita.com/sensuikan1973/items/70a8d954f24e89156d15">ReactアプリのリンクをサクッとTwitterで魅せる</a></li> </ul> ckoshien