Taka blog

プログラミングとか

個人開発のWEBサービスをどこにホスティングするか(本番環境はどこのサーバを借りてデプロイするか)

Github pages

  • 無料
  • デプロイが一番簡単

  • リポジトリがprivateだと公開できないのでpublicにする必要がある

  • そのままではドメイン直下以外のURLを扱えないので、ドメイン直下以外のURLに遷移すさせたい場合は404エラー時の動作を書き換える必要がある

Vercel

  • 無料
  • デプロイが簡単

  • 無料プランだと広告を付けられない

  • 有料プランは$20〜

Netlify

  • 無料

  • 日本にリージョンが無いのでアクセスに時間がかかる

Cloudflare pages

  • 無料
  • 広告を付けられる

Firebase Hosting

Cloud Run

  • 無料枠あり
  • アクセス数が少なければ数円程度
  • 他のGoogle Cloud サービスを簡単に使える

  • 無料枠はあるが決済手段の登録が要る?

WSL2でNode.jsの環境構築・npmのインストール

「nvm」と「n」の2種類のインストール方法がある。
GitHubのスター数で勝り、Microsoft公式で推奨されている「nvm」の手順を記載する。

前提:WSL2を使用可能なこと takaittakait.hatenablog.com

# インストールするパッケージ一覧を更新
sudo apt update

sudo apt install -y curl

# nvmをインストール
curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/master/install.sh | bas

# nodejsとnpmをインストール
nvm install --lts

npm --version

# yarnをインストール
curl -sS https://dl.yarnpkg.com/debian/pubkey.gpg | sudo apt-key add -
echo "deb https://dl.yarnpkg.com/debian/ stable main" | sudo tee /etc/apt/sources.list.d/yarn.list
sudo apt install -y yarn

WSL2にGoをインストールする。またはバージョンアップする。

WSL2環境で、Goをインストールして、プロジェクトを作り、実行するところまで行います。
既にインストール済みでバージョンアップしたい場合にも、同じ方法が使えます。

公式のインストール手順はこちら

既存のGoを削除

既にインストール済みならこちらから実施してください。

sudo rm -rf /usr/local/go

既存のGoファイルが別のパスにある場合は、そのパスを指定する必要があります。
どのパスにあるか分からない場合は、下のコマンドで探しましょう。

which go

Goインストーラーをダウンロード

sudo wget https://go.dev/dl/go1.18.linux-amd64.tar.gz

1.18の部分でGoのバージョンを指定します。
最新のバージョンをダウンロードしたい場合は、公式の「Download Go for Linux」のリンクアドレスをコピーしましょう。

Goインストーラーを解凍して実行

sudo tar -C /usr/local -xzf go1.18.linux-amd64.tar.gz

こちらも、ダウンロードしたインストーラーのバージョンを指定するようにしましょう。

Goインストーラーを削除

コマンドじゃなくてもOKです。

sudo rm go1.18.linux-amd64.tar.gz

Goを環境変数に追加

ホームディレクトリ配下の.profileのPATHに追加します。

PATH=$PATH:/usr/local/go/bin
export PATH

以下のコマンドを打ち反映させます。

source ~/.profile

Goがインストールできたか確認

go version

WSL2でDockerを利用する + redisをDockerで起動する方法

Dockerをインストール

sudo apt update
sudo apt  upgrade
sudo apt  install docker.io

Dockerを起動

sudo  dockerd

redisを起動

sudo docker run --name redis -d -p 6379:6379 redis-server --appendonly yes

redisを操作する

docker exec -it redis bash

execでコンテナのbashに入ったら、このコマンドを入力します。

redis-cli

以降はredisの操作です。データ型によって使えるコマンドは異なります。

keys *

get key1

hgetall key1

redis操作を終了します。

exit

コンテナ内での操作を終了します。

exit

Dockerを終了

Ctrl + C

Go言語入門 - 変数

Go言語入門のうち、変数についてまとめています。

 

Goの公式仕様はこちらに載っています。 https://go.dev/ref/spec

 

 

変数とは

変数は、ある型1つと、それに合った値1つを保持するものです。

例えばstring型の変数は、文字列である"a"を保持できます。逆に、数字の1などを保持することはできません。

 

宣言

基本的には、関数内で変数の宣言を行います。

 

main.go例

package main

import "fmt"

func main() {
	s := "a"
	
	fmt.Println(s)
}

 

宣言の方法はいくつかあるので、次はそれを挙げていきます。

 

変数名 := 値

最も使うパターンです。

上のmain.go例で使っているのもこれです。

宣言と代入を行っていて、最もシンプルに書けます。

 

型名が省略されていますが、型は値から自動で推測されます。

  • 値が""で囲まれている(=値が文字列) → string型
  • 値が数字 → int型
  • 値がtrueまたはfalse → bool型

 

右辺が値ではなく、変数名 := 関数名(引数)というパターンもあります。この場合は、関数の宣言で値の型が決まっています。

 

var 変数名 型名

var s string

 

このパターンのように初期値の無い宣言を行うと、値には型のゼロ値が入ります。  

型によってゼロ値は異なり、string型は""(=空文字)、int型は0です。

 

var 変数名 型名 = 値

var s string = "a"

 

var 変数名 = 値

var s = "a"

 

複数の変数を宣言

s, t := "a", "b"

var s, t string

var s, t string = "a", "b"

var s,t = "a", "b"

var (
	s string
	t = "a"
)

 

Next.jsでコードブロックにシンタックスハイライトで色を付ける

編集対象ファイルの全コード

シンタックスハイライトを付ける上で変更するのは、root/pages/blog/[id].tsxファイルのみです。最初に、このファイルの例を貼っています。

import { client } from "../../libs/client";
import type { Article } from '../../types/article';
import styles from '../../styles/Home.module.css'
import Head from 'next/head'
import cheerio from 'cheerio';
import hljs from 'highlight.js';
import 'highlight.js/styles/night-owl.css';
import {endpoint} from '../index'

type props = {
  article: Article,
  highlightedBody:string,
  tableOfContents:content[]
};

type content = {
  text: string;
  id: string;
  name: string;
}

export default function BlogId({article, highlightedBody, tableOfContents}:props) {
  return (
    <div className={styles.container}>

      <Head>
        <title>{article.title}</title>
        <meta name="description" content={article.description} />
        <link rel="icon" href="/favicon.ico" />
      </Head>

      <main className={styles.postmain}>
        <h1 className={styles.title}>{article.title}</h1>
        <div className={styles.toc}>
          <h4>目次</h4>
          <ul id="lists">
            {tableOfContents.map(content => {
              if (content.name == "h2"){
                return <li id={content.name} key={content.id}>
                  <a href={"#" + content.id}>{content.text}</a>
                </li>
              }else if (content.name=="h3"){
                return <ul><li id={content.name} key={content.id}>
                  <a href={"#" + content.id}>{content.text}</a>
                </li></ul>
              }else if (content.name=="h4"){
                return <ul><ul><li id={content.name} key={content.id}>
                  <a href={"#" + content.id}>{content.text}</a>
                </li></ul></ul>
              }
            })}
          </ul>
        </div>
        <div className={styles.post} dangerouslySetInnerHTML={{__html: highlightedBody}}/>
      </main>

    </div>
  );
}

// パス指定
export const getStaticPaths = async () => {
  const data = await client.get({ endpoint: endpoint });
  const articles: Article[] =  data.contents;
  const paths = articles.map((content) => `/`+endpoint+`/${content.id}`);
  return { paths, fallback: false };
};

// データをテンプレートに受け渡す
export const getStaticProps = async (context: any) => {
  const id = context.params.id;
  const res:Article = await client.get({ endpoint: endpoint, contentId: id });

  // autoでシンタックスハイライトを付ける
  const $ = cheerio.load(res.body)
  $('pre code').each((_, elm) => {
    const result = hljs.highlightAuto($(elm).text())
    $(elm).html(result.value)
    $(elm).addClass('hljs')
  })

  const headings = $('h2, h3, h4').toArray()
  const toc:content[] = headings.map((data) => {
    return {
      //@ts-ignore
      text: String(data.children[0].data),
      id: data.attribs.id,
      name: data.name,
    }
  })

  return {
    props: {
      article:res,
      highlightedBody:$.html(),
      tableOfContents:toc
    },
  }
};

各処理の紹介

ハイライト付HTMLの作成

getStaticProps内で、ハイライトされたHTMLを作成します。

  const $ = cheerio.load(res.body)
  $('pre code').each((_, elm) => {
    const result = hljs.highlightAuto($(elm).text())
    $(elm).html(result.value)
    $(elm).addClass('hljs')
  })

  return {
    props: {
      article:res,
      highlightedBody:$.html(),
      tableOfContents:toc
    },
  }

今回は言語を指定せず、オートで言語を判断させています。
$.html()がHTML要素になるので、これを引数として渡せばOKです。

ハイライト付HTMLの表示

export default function XXX の中に記載します。

export default function BlogId({article, highlightedBody, tableOfContents}:props) {
  return (
    <div className={styles.container}>

// 省略

<div className={styles.post} dangerouslySetInnerHTML={{__html: highlightedBody}}/>

まず、引数でハイライト付HTML(例のhighlightedBody)を受け取ります。
これを表示するのに変更が必要な箇所は、例の最後の行のように、ハイライト済みのHTML要素を指定するところだけです。

Next.js×TypeScript×microCMSで目次を作成する

Next.js×TypeScript×microCMSで目次を作成する方法をご紹介します。

例 : https://tkblog.netlify.app/blog/asbz5h_mr1h
目次を追加したい場合は、ブログの個別記事に当たるtsxファイルに記述します。
この記事の例では、/pages/blog/[id].tsxのコードを貼っています。

インストール

コマンドでcheerioをインストールします。

yarn  add cheerio

これでインポートが可能になります。

import cheerio from 'cheerio';

getStaticProps

  const headings = $('h2, h3, h4').toArray()
  const toc:content[] = headings.map((data) => {
    return {
      //@ts-ignore
      text: String(data.children[0].data),
      id: data.attribs.id,
      name: data.name,
    }
  })

$('h2, h3, h4').toArray()のように、$()内に目次として抽出したい要素をカンマ区切りで指定します。
各要素をどのように表示するかはdefault関数で制御します。

textには表示されるテキストが入ります。h2などのタグに挟まれるテキストのことです。
idには自動生成のidが入ります。
nameにはh2h3などが入ります。

HTML

          <ul id="lists">
            {tableOfContents.map(content => {
              if (content.name == "h2"){
                return <li id={content.name} key={content.id}>
                  <a href={"#" + content.id}>{content.text}</a>
                </li>
              }else if (content.name=="h3"){
                return <ul><li id={content.name} key={content.id}>
                  <a href={"#" + content.id}>{content.text}</a>
                </li></ul>
              }else if (content.name=="h4"){
                return <ul><ul><li id={content.name} key={content.id}>
                  <a href={"#" + content.id}>{content.text}</a>
                </li></ul></ul>
              }
            })}
          </ul>

簡略化すると、{tableOfContents.map(content => {return HTML要素}です。
例の様に配列.mapとするとループ処理になり、contentに各要素が入ります。
(content, index)と書けばindexも利用できます。

h2,h3,h4で目次を入れ子にしたかったので、ulタグの中にulタグを入れています。

初心者がNext.js×TypeScript×microCMSでブログ作成

JavaScriptもNext.jsもTypeScriptもmicroCMSも初心者の私が、これらを組み合わせてブログを作成してみました。

ブログのリンク : https://tkblog.netlify.app/

ベースはmicroCMS公式ページ(https://blog.microcms.io/microcms-next-jamstack-blog/)に載っています。
しかし公式の例はTypeScriptではないので、いくつか変更を加えました。

他に追加した機能もあり記事内のコード例に含まれているのですが、それらについては別記事で紹介しようと思っています。

Next.jsのインストールまでは別記事で書いています。 takaittakait.hatenablog.com

目次

  1. 個別記事オブジェクトの型を定義
  2. 記事一覧ページを作成
  3. 個別記事ページを作成

個別記事オブジェクトの型を定義

記事オブジェクトの型を定義するために、新たにファイルを作成しました。

root/types/article.ts

export type Article = {
    id: string
    createdAt: string
    updatedAt: string
    publishedAt: string
    revisedAt: string
    description: string
    title: string
    body: string
    eye_catch: {
      url: string
    }
    tag: string
  }

各フィールドの名前と型は、microCMSのAPIと合わせる必要があります。

記事一覧ページの中身を変更

root/papes/index.tsx

import Head from 'next/head'
import Image from 'next/image'
import Link from 'next/link'
import React from 'react'
import { client } from '../libs/client'
import type { Article } from '../types/article'
import styles from '../styles/Home.module.css'

type props = {
  articles: Article[]
};

export default function Home({ articles }: props) {
  return (
    <div className={styles.container}>

      <Head>
        <title>記事一覧</title>
        <meta name="description" content="Next.jsでブログ作成" />
        <link rel="icon" href="/favicon.ico" />
      </Head>


      <main className={styles.indexmain}>
        <h1 className={styles.title}>記事一覧</h1>

        <div className={styles.grid}>
          {articles.map(article => (
            <div key={article.id} className={styles.card}>
              <Link href={`/blog/${article.id}`}><a className="">
                <Image className={styles.thumbnail} src={article.eye_catch.url || ''} width={600} height={400} alt={article.eye_catch.url}></Image>
                <p>{article.title}</p>
                </a></Link>
            </div>
          ))}
        </div>
      </main>

    </div>
  );
}

export const getStaticProps = async () => {
  const data = await client.get({ endpoint: 'blog' })
  return {
    props: {
      articles: data.contents,
    },
  }
}

ファイル名はindex.tsxで固定です。これからこのファイル内の各処理について解説していきます。

型をインポート

import type { Article } from '../types/article'

先程作成したArticle型をインポートしています。
他のimport行は全て既存のもののインポートなので、書くだけで使えるはずです。

getStaticProps関数

export const getStaticProps = async () => {
  const data = await client.get({ endpoint: 'blog' })
  return {
    props: {
      articles: data.contents,
    },
  }
}

順番が前後してしまいますが、getStaticPropsからご紹介します。
この関数を利用すると、ビルド時にデータを取得し、静的なHTMLを出力できます。リクエストが来る度にいちいちページ生成を行わないので、レスポンスが高速です。

endpointにはmicroCMSのAPIのエンドポイントを入れましょう。これでビルド時にmicroCMSのAPIにリクエストして、記事一覧を取得するようになります。

propsはリクエストして受けとったデータそのものです。フィールドのarticlesに、データに含まれる記事配列を入れています。このフィールド名articlesは後の手順で名前を合わせないといけないので注意が必要です。

Home関数

type props = {
  articles: Article[]
};

export default function Home({ articles }: props) {
  return (
    <div>sample</div>
  );
}

ここで新たな型を定義しています。props型のフィールドarticlesは、getStaticProps内で指定したフィールド名と合わせなければいけません。

Home関数のexportは恐らくJavaで言うpublicです。

Home{}の{}内で引数の設定をしています。
この例ではpropsオブジェクトを引数で受け取り、そのフィールドarticlesを引数内で利用する、という設定です。

関数だと、sampleFunc (name :string):stringというような書き方がより一般的なのかなと思います。
(引数名:引数の型):戻り値の型という形式です。

defaultが付いた関数はJSX.Element型(≒HTML要素?)を返します。なのでreturn();内に記事一覧ページのHTML要素を記述します。

個別記事ページを作成

root/pages/blog/[id].tsx

import { client } from "../../libs/client";
import styles from '../../styles/Home.module.css'
import Head from 'next/head'
import type { Article } from '../../types/article';

type props = {
  article: Article,
};

export default function BlogId({article}:props) {
  return (
    <div className={styles.container}>

      <Head>
        <title>{article.title}</title>
        <meta name="description" content={article.description} />
        <link rel="icon" href="/favicon.ico" />
      </Head>

      <main className={styles.postmain}>
        <h1 className={styles.title}>{article.title}</h1>
        <div className={styles.post} dangerouslySetInnerHTML={{__html: article.body}}/>
      </main>

    </div>
  );
}

// パス指定
export const getStaticPaths = async () => {
  const data = await client.get({ endpoint: "blog" });
  const articles: Article[] =  data.contents;
  const paths = articles.map((content) => `/blog/${content.id}`);
  return { paths, fallback: false };
};

// データをテンプレートに受け渡す
export const getStaticProps = async (context: any) => {
  const id = context.params.id;
  const res:Article = await client.get({ endpoint: "blog", contentId: id });

  return {
    props: {
      article:res
    },
  }
};

また順番が前後しますが、getStaticPathsgetStaticPropsBlogIdの順で見ていきます。

getStaticPaths

// パス指定
export const getStaticPaths = async () => {
  const data = await client.get({ endpoint: "blog" });
  const articles: Article[] =  data.contents;
  const paths = articles.map((content) => `/blog/${content.id}`);
  return { paths, fallback: false };
};

名前通り、固定記事パスを指定しています。blogはmicroCMSのAPIのエンドポイント、content.idが各記事の識別子です。

getStaticProps

// データをテンプレートに受け渡す
export const getStaticProps = async (context: any) => {
  const id = context.params.id;
  const res:Article = await client.get({ endpoint: "blog", contentId: id });

  return {
    props: {
      article:res
    },
  }
};

BlogId(defaultの関数)

export default function BlogId({article}:props) {
  return (
    <div className={styles.container}>

      <Head>
        <title>{article.title}</title>
        <meta name="description" content={article.description} />
        <link rel="icon" href="/favicon.ico" />
      </Head>

      <main className={styles.postmain}>
        <h1 className={styles.title}>{article.title}</h1>
        <div className={styles.post} dangerouslySetInnerHTML={{__html: article.body}}/>
      </main>

    </div>
  );
}

index.tsxdefault関数とほぼ同じ構造です。
<div className={styles.post} dangerouslySetInnerHTML={{__html: article.body}}/>で、個別記事のHTML要素をテンプレート内に入れています。

WSL2でNext.jsの環境構築とアプリケーション作成

wsl2環境で、0からNext.jsの環境構築とアプリケーションの作成まで行う方法をご紹介します。コマンドはそのままコピー&ペーストして使えるようになっているはずです。

npmとyarnをインストール

takaittakait.hatenablog.com

Next.jsアプリケーションを作成

TypeScriptを用いたい場合は、コマンドのオプションに--tsを追加 します。

npx create-next-app app-name 

Windows10でWSL2(Ubuntu)をインストールし、Linux開発環境を構築する方法

Windows10でLinux開発環境を構築する方法を紹介します。
コマンドは全てコピペできるように書いています。

目次

  1. WSL2をインストールする
  2. Ubuntuを起動する
  3. Visual Studio Codeの設定をする

WSL2をインストールする

コマンドプロンプトを管理者権限で開く

検索バーにcmdを入力すれば、コマンドプロンプトが表示されます。
それから管理者として実行をクリックします。

WSL2をインストールするコマンドを入力する

wsl --install

PCを再起動する

私の場合は、上記コマンドを実行し再起動した後、アプリ一覧にUbuntuが追加されました。

Ubuntuを起動する

上の手順が完了していれば、アプリ一覧にUbuntuが追加されるので、そちらを開きます。

Ubuntuの初回起動時は、ユーザー名とパスワードを設定しなければいけません。
次回以降にこのセットが必要なので、忘れないようにメモしましょう。

Visual Studio Codeの設定をする

Visual Studio Codeをインストールする。

コードエディタとしてVisual Studio Code(以下 VSCode)をインストールします。
インストールは下記の公式リンクから行ってください。 Visual Studio Code – コード エディター | Microsoft Azure

VSCodeからWSL2環境に接続する

VSCode拡張機能「Remote-WSL」をインストールしてください。

左下緑色の枠内をクリックし、「Open folder in WSL」を選択します。

これで、VSCodeを用いてWSL2環境で開発できるようになります。

以降はVSのCodeターミナルにコマンドを入力することになります。
もしそこでコマンドが権限で弾かれた場合は、頭にsudoを付けて実行してください。
ここで必要なパスワードが、Ubuntu初回起動時に設定したパスワードです。