Slide 1

Slide 1 text

サーバーレスAPI(API Gateway+Lambda)とNext.jsで 個⼈ブログを作ろう! 2024.7.20 製造ビジネステクノロジー部 髙橋俊⼀ a.k.a. shuntaka

Slide 2

Slide 2 text

⾃⼰紹介 2 shuntaka/髙橋俊⼀ 19年8⽉クラメソ⼊社 製造ビジネステクノロジー部 基本的にAWSでサーバーサイド開発 ‧IoTバックエンド/エッジソフトウェア開発 ‧サーバレス/コンテナ開発 ‧データ基盤開発 ツール開発: ddbrew/gh-p2/oax/s3-concat/preview-asciidoc.vim … shuntaka.dev/who

Slide 3

Slide 3 text

3 「Speakerdeck shuntaka」で検索 or https://spekerdeck.com/shuntaka

Slide 4

Slide 4 text

ご理解頂きたいこと 4 ‧フロントエンド/デザインは、サーバサイドエンジニアが 頑張ってみた...くらいの温度感で、要素少なめです😿 ‧サーバーレスAPIと表記しているところは、Amazon API Gateway+AWS LambdaのREST APIを指します

Slide 5

Slide 5 text

⽬次 5 ‧過去の個⼈ブログ ‧現在の個⼈ブログ ‧経験から学んだこと ‧未来へ向けて ‧まとめ

Slide 6

Slide 6 text

⽬次 6 ‧過去の個⼈ブログ 👈 ‧現在の個⼈ブログ ‧経験から学んだこと ‧未来へ向けて ‧まとめ

Slide 7

Slide 7 text

作る経緯 7 2018年前職時代 Web企業のミートアップにポツポツ参加し始め、 参加ブログを書きたくなる🐥 当時Goが好きだったので、静的サイトジェネレータのHugoに興味 を持つ

Slide 8

Slide 8 text

構成 8 静的サイトジェネレーター(Hugo)を使って構築 (独⾃ドメイン、HTTPS) 2. ビルド‧デプロイ 1. push

Slide 9

Slide 9 text

外観 9 作った当初は書いていたが、しだいに全く書かなくなり、ドメ イン解約😱

Slide 10

Slide 10 text

ブログ運⽤が続かなかった理由 10 振り返ると感じた続かない理由 ‧サイト⾃体へ愛着がなかった ‧モチベーションに繋がる仕組みを⾃分が作らなかった

Slide 11

Slide 11 text

⽬次 11 ‧過去の個⼈ブログ ‧現在の個⼈ブログ 👈 ‧経験から学んだこと ‧未来へ向けて ‧まとめ

Slide 12

Slide 12 text

作る経緯 12 技術ブログといえばZenn!影響をかなり受けてま す。Zennを触り、個⼈ブログに以下の体験を検証 /実装したいと感じた。 ‧Gitプッシュ後のサイトへ反映速度 ‧記事プレビューの体験の良さ

Slide 13

Slide 13 text

作る経緯 13 プラスで漠然と思っていたこと ‧⾃分がほぼ毎⽇使うWebアプリを作り たかった(改善欲の刺激) ‧業務でサーバーレスAPIを書いているの で趣味で使いたい ‧技術検証できる庭が欲しい ‧業務でありそうな要件で実装したい

Slide 14

Slide 14 text

構成 14

Slide 15

Slide 15 text

構成 15 ・お財布に優しい(ほぼ無料枠運用) ・Vercel/Next.jsのISRとの相性(コールドスタート問題の緩和) ・個人でお手軽にクラサバ構成が取れる  ・Fly.io, Deno deploy, Cloudflare workerなどの選択肢   → 自分の業務シナジーを考慮

Slide 16

Slide 16 text

外観 16 shuntaka.dev

Slide 17

Slide 17 text

現在のブログで注⼒したこと 17 ‧Gitプッシュ後サイトへ即反映 ‧記事プレビューの体験の良さ ‧サイト⾃体への愛着 ‧ モチベーションに繋がる仕組み

Slide 18

Slide 18 text

現在のブログで注⼒したこと 18 ‧Gitプッシュ後サイトへ即反映 👈 ‧記事プレビューの体験の良さ ‧サイト⾃体への愛着 ‧ モチベーションに繋がる仕組み

Slide 19

Slide 19 text

Gitプッシュ後サイトへ即反映 19 GitHub Appsから直接DBにデータを投⼊することで実現 サイトや過去記事ビルドが必要なくなる

Slide 20

Slide 20 text

WebアプリからのApps連携 20 ※ GIFなのでPDFだ と再⽣できません

Slide 21

Slide 21 text

Webアプリからの連携シーケンス 21 https://shuntaka.dev/shuntaka/articles/01ezm5k2rt1jm6zbsewm33r0xw

Slide 22

Slide 22 text

Webhook検証 22 Webhookの検証もしておく と安全 ‧GitHubでwebhookシーク レットを指定 ‧ボディ部を上記を使い HMAC-SHA256でハッシュ ‧X-Hub-Signature-256と⼀ 致検証

Slide 23

Slide 23 text

Webhookのトラブルシューティング 23 GitHub Apps上からペイロードの確認可能

Slide 24

Slide 24 text

現在のブログで注⼒したこと 24 ‧Gitプッシュ後サイトへ即反映 ‧記事プレビューの体験の良さ 👈 ‧サイト⾃体への愛着 ‧ モチベーションに繋がる仕組み

Slide 25

Slide 25 text

記事プレビューの体験の良さ 25 静的サイトジェネレータのファイルシステム変更監視による プレビューは顧客が求めていたもの...⾃作するならどうする か... エディタにより依存させれば、汎⽤性はないが良い体験が⽣ まれそう...

Slide 26

Slide 26 text

マークダウンパーサー 26 ‧Rustで⾃作を試みるが断念。markdown-itを拡張 ‧困ったらzenn-dev/zenn-editorの実装を参考にさせて頂い ている... ‧独⾃で欲しい機能を拡張できる(PlantUML,SpeakerDeckの 指定スライドプレビューなど) ‧今後⾔語処理系の学習でリベンジしたい...

Slide 27

Slide 27 text

Vimとの連携 27 denops.vim Vim/NeovimプラグインをTypeScript(deno) で書けるというエコシステム denoはnpmのパッケージと互換性があるた め、前述パーサーを流⽤できる

Slide 28

Slide 28 text

リアルタイムプレビュー(Vim/Neovim) 28

Slide 29

Slide 29 text

リアルタイムプレビュー(Vim/Neovim) 29 ※ GIFなのでPDFだ と再生できません

Slide 30

Slide 30 text

現在のブログで注⼒したこと 30 ‧Gitプッシュ後サイトへ即反映 ‧記事プレビューの体験の良さ ‧サイト⾃体への愛着 👈 ‧ モチベーションに繋がる仕組み

Slide 31

Slide 31 text

作って気づく愛着 31 結論、⾃分でデザインして時間をかけることで愛着が醸成さ れた。 これは意識して訳ではなく、Next.jsでやると決めた瞬間デザ インは必要に迫られ、やらざるを得なかった。

Slide 32

Slide 32 text

デザイン 32 Figmaの学習。⾼評価のUdemyの動画を⼿を動かしながら慣 れていく。(※ 2020年時点なので注意)

Slide 33

Slide 33 text

デザイン 33 オブジェクトコネコネ...⽇曜⼤⼯感...

Slide 34

Slide 34 text

ダークモード 34 CSS Variablesを使って作成 配⾊はGitHubのダークモー ドを参考に

Slide 35

Slide 35 text

OGP 35 ‧サムネイル設定 → そのまま表⽰ ‧サムネイル未設定→タイトル⽂字列をCloudinaryのURL/画像 に埋込 → URLは署名を含められる 任意の⼈が⽂字列を埋め込むことはできない

Slide 36

Slide 36 text

現在のブログで注⼒したこと 36 ‧Gitプッシュ後サイトへ即反映 ‧記事プレビューの体験の良さ ‧サイト⾃体への愛着 ‧ モチベーションに繋がる仕組み 👈

Slide 37

Slide 37 text

アクセス解析導⼊ 37 ‧Google Analytics ‧Search Console連携

Slide 38

Slide 38 text

必要なこと 38 静的サイトジェネレータならデフォルトである。流⽯。 ‧sitemap⽣成(->Search Consoleへ登録) ‧RSS⽣成

Slide 39

Slide 39 text

現在のブログで注⼒したこと 39 ‧Gitプッシュ後サイトへ即反映 ‧記事プレビューの体験の良さ ‧サイト⾃体への愛着 ‧ モチベーションに繋がる仕組み

Slide 40

Slide 40 text

⽬次 40 ‧過去の個⼈ブログ ‧現在の個⼈ブログ ‧経験から学んだこと 👈 ‧未来へ向けて ‧まとめ

Slide 41

Slide 41 text

サービス運⽤する楽しさ 41 ‧⾃⼰表現と振り返りの楽しみ ‧特定の⼈にしか⾒られない⼼理的安全性 ‧ちゃんと書いた記事はオーガニック検索流⼊ ‧検索パフォーマンスから読者の興味を分析 ‧意外な⼈気記事の発⾒(例:Zig⾔語、platformio記事)

Slide 42

Slide 42 text

技術を試す遊び場 42 ‧利害関係者がいないため、新しいライブラリや開発⼿法を 試しやすい ‧既存システムで試すことで、使うだけとは異なった視点(開 発や運⽤に適⽤した場合)の体験が得られる ‧いろんなサイトを参考にするようになる。プラックティス の塊。(今回だとGitHub/Zenn/Qiitaなど...) ‧ある程度⼈にみられることを考慮された動くサンプルが⼿ 元にあるメリット

Slide 43

Slide 43 text

必要は発明の⺟ 43 ‧無から必要を⽣み出すのは難しい ‧ブログ開発/運⽤することで必要が⽣まれやすい  ‧可処分時間やお財布を考えて⼯夫の余地を探す等 通して、様々ブログ記事やCLI開発をした... 詳細は過去記事で

Slide 44

Slide 44 text

実際作りっぱなし... 44 ‧最初は熱中、その後はライブラリ、ランタイム更新対応 ‧業務や登壇の機会を利⽤してブースト ‧⾃分が興味あること/ないことのメタ認知

Slide 45

Slide 45 text

SaaS周りの注意は必須... 45 ‧スマホ未圧縮画像配信で、ネットワーク帯域を使いすぎて料 ⾦発⽣経験 ‧クレカ登録不要でもお試し系は、解約忘れ注意。⾃動停⽌ しない場合あり、プロジェクトが消せず、放置期間含めて請求 が発⽣したり、、 → 契約とGoogleカレンダー登録は同時に ‧新興の海外系SaaSだとサポートもあまり期待は出来ない

Slide 46

Slide 46 text

セッション概要 46 ‧過去の個⼈ブログ ‧現在の個⼈ブログ ‧経験から学んだこと ‧未来へ向けて 👈 ‧まとめ

Slide 47

Slide 47 text

登場するコードはこちらに 47 https://github.com/shuntaka9576/devio2024-sample-code

Slide 48

Slide 48 text

試したいこと 48 技術選定をしたのは4年前なのでリプレースするために、技術検 証してみる ‧同じ解決策でも新しいやり⽅へ ‧今⾃分がワクワクする構成へ

Slide 49

Slide 49 text

試したいこと 49 ‧サーバサイドJSマルチランチタイム ‧エコシステムの成⻑への追従 ‧認証 ‧認知負荷へ対応

Slide 50

Slide 50 text

試したいこと 50 ‧サーバサイドJSマルチランチタイム 👈 ‧エコシステムの成⻑への追従 ‧認証 ‧認知負荷へ対応

Slide 51

Slide 51 text

JSランタイムの選択肢 51 WinterCGによる⾮Webブラウ ザJSランタイム標準化の流れ Deno/Bun/Node.jsの三つ巴 開発者体験はまだNode.jsの印 象。今後は分からない。 Node.jsを使いつつ、乗り換え 可能な選択肢を持つ。

Slide 52

Slide 52 text

HTTPサーバー/ルーティング 52 Honoを利⽤(Lambda⽬線だとHTTPサーバー部分は含まない) ‧作ったアプリがコンテナや別JSランタイム上で検証可 ‧活発なコミュニティ ‧Web標準API/マルチランタイム  → ミドルウェア含めランタイム互換性が⾼い  → 利⽤者‧開発者⼈⼝が多くなりそう(?)  → エコシステムが充実しそう!

Slide 53

Slide 53 text

試したいこと 53 ‧サーバサイドJSマルチランチタイム ‧エコシステムの成⻑への追従 👈 ‧認証 ‧認知負荷へ対応 ‧その他

Slide 54

Slide 54 text

Nodeエコシステム 54 ⾼速なツールチェインの採⽤ ‧pnpm → 並列実⾏ヘルパー不要なので使っているが、拘りなし(多様 性がありそう) ‧Biome(Rust製で⾼速) → 異次元な速さ+LSPで最⾼。BE的には⼗分。

Slide 55

Slide 55 text

ESMを使う 55 ESMしかサポートしないライブラリも少なくない CDKもtsx経由で実⾏すればESMで実⾏可能 // cdk.json { "app": "pnpm tsx bin/cdk.ts" … }

Slide 56

Slide 56 text

56 const getConfig = async (): Promise => { const secretsAndVariables = await getVariablesAndSecrets(); const parseResult = configSchema.safeParse({ auth: { rpID: secretsAndVariables.variables.rpId, rpName: secretsAndVariables.variables.cookieDomain, }, ... }); ... return parseResult.data; }; export const config = await getConfig(); ESMを使う to level awaitの活⽤ ‧起動時環境変数 /SSM取得&バリデー ション(設定し忘れ防 ⽌) ‧以後config変数を Lambdaでグローバル 変数キャッシュ

Slide 57

Slide 57 text

モノレポ構成 57 CIの⼀部共通化やリポジ トリ間のCDが少なくな り良い。 GHAは数年で共通化の余 地が増えた。横展可能な 秘伝タレを作ると良い。 ‧Reusing workflows ‧Composite action name: Setup node description: Setup Node and restore cache runs: using: 'composite' steps: - uses: actions/setup-node@v4 with: node-version-file: './.node-version' - uses: pnpm/action-setup@v4 - name: Restore node modules uses: actions/cache@v4 id: cache_dependency env: cache-name: cache-dependency with: path: '**/node_modules' key: ${{ runner.os }}-build-${{ env.cache-name }}-${{ hashFiles('pnpm-lock.yaml') }} - name: Install node modules

Slide 58

Slide 58 text

試したいこと 58 ‧サーバサイドJSマルチランチタイム ‧エコシステムの成⻑への追従 ‧認証 👈 ‧認知負荷へ対応 ‧その他

Slide 59

Slide 59 text

認証 59 ‧Firebase Authenticationを採⽤ ‧⾃分⽤にダッシュボードが欲しくて実装

Slide 60

Slide 60 text

認証 60 パスワード⼊れるの⾯倒問題

Slide 61

Slide 61 text

パスキーの検証 61 技術検証でWebAuthnの仕様を使ったパスキー認証を試す ライブラリは以下を利⽤ ‧hono_session(Honoミドルウェア) ‧SimpleWebAuthn SimpleWebAuthnのexampleを動かしつつ流れを把握... ただ、永続化していないので、DynamoDBで永続化

Slide 62

Slide 62 text

パスキーの検証 62 ‧challengeをユーザーのデバイ ス(認証器)に 保存された秘密鍵で 署名を作成 ‧サービス側で保存された公開鍵 を⽤いて認証

Slide 63

Slide 63 text

パスキーの検証 63 ※ GIF iCloudキーチェーンを使って、 ⽣体認証でサインアップ/ログイン デモ iCloudキーチェーンで同期される ため、Mac/iPhoneでログイン可

Slide 64

Slide 64 text

試したいこと 64 ‧サーバサイドJSマルチランチタイム ‧エコシステムの成⻑への追従 ‧認証 ‧認知負荷へ対応 👈 ‧その他

Slide 65

Slide 65 text

⾃作ブログは開発頻度にムラあり 65 実際に⾃作ブログの開発頻度はときどきになるので、できれ ば認知負荷を下げたい... try! catch! error! unknown! 😱

Slide 66

Slide 66 text

関数型スタイルTypeScript 66

Slide 67

Slide 67 text

関数型スタイルTypeScript 67 ‧neverthrow ‧fp-ts(Effectと統合) ‧Effect → Effectを試す

Slide 68

Slide 68 text

Effectとは 68 関数型プログラミングの影響を受けて以下をサポート ‧最⼤限の型安全性(エラー処理を含む) ‧テストユーテリティ ‧o11y機能 エコシステムが重厚⻑⼤

Slide 69

Slide 69 text

69 Effectを試す interface Effect 成功時の型 依存するデータ型 (本セッションでは割 愛) 失敗時の型 Effect型について。近いのがRustのResult型。

Slide 70

Slide 70 text

70 Effectを試す 関数の合成 1 つの関数の出力を受け取り、それをパイプライン内の次の関 数の入力として渡します。これにより、複数の関数を連結して複 雑な変換を構築

Slide 71

Slide 71 text

71 Effectを試す const effects = pipe( { isLoggedIn: session.get('isLoggedIn'), userID: session.get('userID'), }, Schema.decodeUnknownEither(toValidSchema), Effect.flatMap(workflow) ); const result = await Effect.runPromise(effects); pipeを使って関数を合成する

Slide 72

Slide 72 text

72 const effects: Effect.Effect<{ isLoggedIn: boolean; user: { userName: string; }; }, DataBaseUnknownError | ParseError | NotFoundUserError, never> 先ほどのeffects変数の型推論。赤枠がハンドリングされていない ことが自明。副作用の存在が分かる。 Effectを試す

Slide 73

Slide 73 text

73 関数の振る舞いを予測可能に 関数ができることはすべて、その シグネチャに書かれている。関数 は引数を受け取り、値を返す。そ れ以外は特にないもしない。/ (2.12. 信頼される純粋関数より) → 関数の振る舞いを予測可能に し、副作用を避ける

Slide 74

Slide 74 text

74 const effects = pipe( ... Effect.match({ onSuccess: (result) => ({ status: 200, body: { isLoggedIn: result.isLoggedIn, user: result.user }, }), onFailure: (error) => { switch (error._tag) { case 'ParseError': return { status: 200, body: { isLoggedIn: false } }; case 'NotFoundUserError': return { status: 400, body: { isLoggedIn: false } }; case 'DataBaseUnknownError': return { status: 500, body: { code: 'InternalServerError' } }; } }, }) ); エラーハンドリング漏れを防ぐことができる。日曜大工も安心。 ハンドリングをすれば、 第⼆引数はnever型に Effectを試す

Slide 75

Slide 75 text

試したいこと 75 ‧サーバサイドJSマルチランチタイム ‧エコシステムの成⻑への追従 ‧認証 ‧認知負荷へ対応 ‧その他 👈

Slide 76

Slide 76 text

76 その他やってみたいこと ‧画像の最適化をしてネットワーク帯域を減らしたい ‧スライド機能 ‧全⽂検索(meilisearch, algoria)

Slide 77

Slide 77 text

77 その他やってみたいこと ベアメタルk8sに載せたい →

Slide 78

Slide 78 text

⽬次 78 ‧過去の個⼈ブログ ‧現在の個⼈ブログ ‧経験から学んだこと ‧未来へ向けて ‧まとめ 👈

Slide 79

Slide 79 text

まとめ 79 過去 現在 未来?

Slide 80

Slide 80 text

まとめ 80 ‧ブログの運⽤(アクセス解析/SEO/デザイン) ‧技術試す遊び場(調査/検証/改善/業務/メタ認知) ‧発明(ブログ執筆/登壇ネタ/ツール開発)

Slide 81

Slide 81 text

ご清聴ありがとうございました& お付き合い頂きありがとうござ いました🥹 81

Slide 82

Slide 82 text

レビュー協⼒/参考⽂献 82 レビュー協⼒ ‧Kyo さん (とっ散らかっていたところをまとめて下さり圧倒的感謝!) 参考⽂献 ‧Zennを⽀える技術とサービス構成 ‧TSKaigi関連  ‧TypeScript 関数型スタイルでバックエンド開発のリアル  ‧Step by Stepで学ぶ、ADT(代数的データ型)、モナドからEffect-TSまで゙  ‧複雑なビジネスルールに挑む:正確性と効率性を両⽴するfp-tsのチーム活⽤術  ‧Effectで作る堅牢でスケーラブルなAPIゲートウェイ ‧Deno Deploy で WebAuthn を使ったサイトを作ってみた ‧パスキーのすすめ

Slide 83

Slide 83 text

83