tekitoumemo’s diary

思ったことを書くだけ。長文版Twitter

universalモードでのaxiosのエラーハンドリングを共通化する

universalモードの場合、SPAのハンドリング、SSRのハンドリングをしなければならない。

SPAの場合はビューで制御して
ビュー - NuxtJS

SSRの場合はnginxのerror_pageディレクティブとか使って制御する必要がある

フロントもSSR、SPAを判定してエラーハンドリングをしなければいけないのですが、めんどくさいのでそこらへんの判定を無視する雑なハンドリング方法を書く。雑実装なので非推奨

static/error.html

<body>
    エラーっす
</body>

plugins/axios.js

export default function({ $axios, redirect }) {
  $axios.onError((err) => {
    const code = parseInt(err.response && err.response.status)
    redirect(code, '/static/error.html')
  })
}

nuxt.config.js

plugins: [
    { src: '~/plugins/axios.js' },
]

あとはnginxとかでerror_pageディレクティブとか使ってハンドリングする。errorメソッドでハンドリングしても良いが、axiosの例外はSSRのときに500で拾ってしまうので正しいステータスコードをサーバーに伝えるため無理やり指定のstatusコードにredirectしてる。

雑すぎるのでおすすめしない。

[参考]
https://axios.nuxtjs.org/helpers

[NustJS]外部ファイルからstoreにアクセスする

NuxtJSではコンポーネントやplugins、middlewareなどなど、contextにアクセス出来ないことがある。つまりstoreにアクセス出来ない。

やり方はいろいろあるが、自分なりの落とし所を書く。

globalで使う(やばい)

.eslintrc.js

globals: {
    $store: true,
    ...
}

plugins/global_store.js

export default function({ store }) {
  global.$store = store
}

nuxt.config.js

plugins: [
    { src: '@/plugins/global_store.js', ssr: false },
]

これでこう使う

export default class Hoge {
    static hoge() {
        $store.dispatch('hogehoge');
    }
}

シャローコピーだしまぁ良いかなーと思ったけど、状態のように常に変わるようなものをグローバルに置くのはまぁヤバイ。コンポーネントではthis.$store$storeどっちも使えるのがシビれる(悪い意味で

onNuxtReadyで初期化

let store
if (process.client) {
  window.onNuxtReady(({$store}) => {
    store = $store
  })
}

export default class Hoge {
    static hoge() {
        store.dispatch('hogehoge');
    }
}

なんか絶対よくない気がするけど、ググってもonNuxtReadyがなんだかわからない。公式のドキュメントがあれば読みたい。却下。

constructorで初期化

export default class Hoge {
  constructor(store) {
    this._store = store
  }
  get $store() {
     return this._store
  }
}

無難。staticで使えないので却下。

シンプルにインスタンス作る

export default class Hoge {
    static get $store() {
        if (!this._store) {
            this._store = new Vuex.Store(...)
        }
        return this._store
    }
}

無難でシンプル、が結果的には最初の実装と同じようにインスタンスはグローバルに。インスタンスメソッドからはthis.constructor.$storeで使う。this.$storeインスタンスが変わるのでシュチュエーションによっては悪い例だけど、今回は特定のclass経由でのみstoreをいじるのでまぁおっけいかも?

pluginsでstoreを注入

plugins/hoge.js

import Vue from 'vue'
export default ({ store }) => {
    Hoge.$store = store
    // Vueインスタンスとして使いたければ
    // Vue.prototype.$hoge = Hoge
    // Vue.prototype.$hoge.$store = store
}
export default class Hoge {
    static hoge() {
        $store.dispatch('hogehoge');
    }
}

Nuxtの初期化フェーズでクリーンなcontextとして扱えるのでこの方法が一番しっくりくる。別インスタンス作ってしまうとchromeのvuedevtoolでstateが確認出来ないので困る。

[参考]
Accessing the store from an external file · Issue #2005 · nuxt/nuxt.js · GitHub

正規表現を使ってディレクトリ内のファイルをrequire出来るようにする

requireでは、ファイル名を直接指定する必要がある。

require

const hogehoge = require('./hoge/hogehoge.js')

たまーに動的に使いたいときがある(NuxtJSでVeeValidateでカスタムルール作るときとか)。
こう使ってたりすることが多い。

names.forEach(name => require(`./hoge/${name}.js`))

これだとnamesはどこで作るのか迷う。余談だがNuxtJSは設定より規約を重視してるので規約をつくった方がよい。だからnames作りたくない

require.context

独自のコンテキストを作れる。早速使い方。

const context = require.context('./hoge/', true, /\.js$/)
context.keys().forEach((key) => {
  // const hoge = require(`./hoge/{key}`) これとほぼ同じ(これじゃ実際動かない
  const hoge = context(key).default
})

左からディレクトリパス、サブディレクトリも検索するかどうか、ファイルを照合する正規表現

これをうまく使えばNuxtJSのpagesでroutingを生成するように規約ベースでなんやかんや出来る!

[参考]
Dependency Management | webpack

dotnet build、runで起きるエラーの対処法

いつも忘れるいつもググるから備忘録

コピーできません

warning MSB3026: "obj/Debug/{app}""bin/Debug/{app}" にコピーできませんでした。1000 ミリ秒以内に 1 回目の再試行を開始します。

多分どっかのプロセスが握ってるので、ブチ消す。

rm -rf bin

Kestrel動きません

crit: Microsoft.AspNetCore.Server.Kestrel[0]
      Unable to start Kestrel.
System.InvalidOperationException: HTTPS endpoints can only be configured using KestrelServerOptions.Listen().

使いたいポートが空いてない。開ける

lsof -i:5000
kill PID

10万PVの収益や掛かる費用など公開する

先月、約8万PV、2.2万ユーザー訪問がありました(タイトル嘘

※2月は10万PVちゃんと超えました(パチパチ👏
f:id:tekitoumemo:20200303002858p:plain

10万PVも十分見込める範囲内なのでここまできた考察と収益やサーバーの維持費、その他サーバーにおけるコストなど公開します。

対象サイト

mygkrnk.com

アナリティクス

このサイトは1人のユーザーが何ページも跨ぐサイトではないので、ユーザー数が実際の流入になります。現状、流入は多いけど直帰、離脱率が比較的高い傾向にあります。1人のユーザーが3.5ページ閲覧しているということになります。見てもらえればわかるのですが、SEOが強く、その他の流入SNS等)は弱いです。

ユーザー数

f:id:tekitoumemo:20200207124923p:plain

PV数

f:id:tekitoumemo:20200207124926p:plain

Search Console

クリックされているキーワードを確認します。「洋楽ランキング 2020」というキーワードが多くクリックされていることがわかります。ユーザーが最新情報を検索する場合は「最新」ではなく西暦を検索する傾向ですが、年初ということもあるので中盤に差し掛かると「最新」と検索する人が増えるかもしれません。「洋楽ランキング」でも3位と上位に位置しているものの、クリック数がそこまで多くないので「最新の洋楽ランキングを知りたい」と思っているユーザーが多いことがわかります。

f:id:tekitoumemo:20200207130241p:plain

SEO頑張った過程については以下をご覧ください。
tekitoumemo.hatenablog.com

サーバー代

f:id:tekitoumemo:20200207132516p:plain

サーバーとデータベースはAzureを利用しているのですが、月2000円程度かかっています。VPSなど使えばまだ安くできると思うのですが、CIとかその他もろもろのインフラを管理するのが大変なのでPaas使えると楽ですね。現状このプランでも十分すぎるほど捌けているのでまだまだ余裕があります(後ほど負荷について記載します)。ちなみにLinuxを選んでいるのでここまで安いのですが、Windowsサーバーになるとこれの4〜5倍します。

負荷

平均応答時間

f:id:tekitoumemo:20200207142929p:plain

大体300ミリ秒前後なので、そこまでストレスが掛かる時間ではないのかなと思います。ただし、PageSpeed Insightsでは200ミリ秒以下は遅いと判断されます。

サーバーの応答時間は 200 ミリ秒以下に抑える必要があります。

CPU

f:id:tekitoumemo:20200207143343p:plain

10%以下に抑えられているので大丈夫でしょう。グラフでちょくちょく負荷が多くみられるのは夜間バッチによる高負荷が原因です。今後改善の予知がありそう。

メモリ

f:id:tekitoumemo:20200207143614p:plain
60〜70%の間を行き来しています。プランでは1.75 GBなので約1GB使っていることになります。リアルタイムの訪問数が40近くいく時があるのですが、まぁ耐えられているんだろうなぁというところです。

データベース

f:id:tekitoumemo:20200207144205p:plain
DTUはAzure特有の単位で以下の通りになります。

DTU(Database Transaction Unit)という単位で定義され、CPU、メモリ、I/Oの組み合わせからなります。
DTUが大きくなるほど、コンピューティングリソースが多く使えるようになり、標準で付属するストレージ、追加できるストレージも大きくなります。後述するように、ダウンタイムを発生させずにスケールの変更ができます。

1%台と全然問題ない結果となっています。

その他

パフォーマンス改善については以下に書きましたので興味があれば
tekitoumemo.hatenablog.com

収益

一番気になるところだと思います。ずばり

3,882円

でした。結局はクリックが増えれば収益も増えるので大きく前後しそうです。PVに大してクリック数が160と相当少ないので、ここら辺が増えればもっと収益が上がるハズです。

Adsenseの画像は載せられないのでご了承ください。

利益

3,882 - 2,229 = 1,653円
夢がないですねー。逆にいうと元を取るのに5万PVぐらい稼がなければいけないので広告で利益をあげるのはかなり厳しい気がします。ここら辺は胡散臭いい記事が多すぎるので、騙されないようにしましょう。

感想

サーバー台の元が取れれば御の字
儲かるにはAdsense等の広告以外で考える必要あり
.Net Coreだから低予算で捌けてるのか、他のフレームワークはどうなのか気になる
10万いくとそれっぽいデータが取れるので次仕掛けることが明確になって良い

C#でmarkdownを書く(Markdig)

サイトを運用する人あるあるで

「ガイドとかヘルプ、マジめんどくせ〜」

ってなりませんか?僕はめっちゃなります、
CSSとかマークアップがとりあえずだるいし、wordpress運用してAPIで取り込むとかもだるいです。
今回はC#markdownが書けるライブラリを紹介します。ちなみに今のご時世MVCが少なくなってきているので
フロントエンドだったらmarkedおすすめです(僕はブログのネタ用でC#で書きました。)

C#Markdownが書けるライブラリ

Markdigってやつです。
github.com

とりあえずNuget

dotnet add package Markdig

使う

using Markdig;
var markdown = File.ReadAllText(path);
var html = Markdown.ToHtml(markdown);
Console.Write(html);
/*
    markdown -> #見出しです。
    html -> <h1>見出しです。</h1>
*/

ちなみに拡張機能を使いたい場合はこうやるらしいです。

// Configure the pipeline with all advanced extensions active
var pipeline = new MarkdownPipelineBuilder().UseAdvancedExtensions().Build();
var result = Markdown.ToHtml("This is a text with some *emphasis*", pipeline);

拡張機能こちらに載ってます。UseAutoLinkstとか便利そうね使わないけど。


実際作ったやつはこんな感じになりました。
f:id:tekitoumemo:20200130225906p:plain