RIT Tech Blog

株式会社RITのエンジニアが知見を共有する技術ブログです。

Next.jsでブラウザバック(戻る)を検知して確認ダイアログを表示する

こんにちは!エンジニアの川野です。

最近はBtoCのサービスを開発していて、ブラウザバック(戻る)で編集中のデータが消えないようにする機能を開発しました。その際の課題と解決策をお話したいと思います。

はじめに

本記事では、Next.jsにおいてユーザがブログなどの投稿画面からブラウザバックするときに確認ダイアログを表示する方法を説明します。

完成イメージ
完成イメージ

上記の機能は、ユーザの誤操作によって編集中の投稿が消えてしまうのを防ぐことで、UXの向上を目的としています。

実現したいこと

  • 投稿画面からブラウザバックするときに「保存されていないデータは削除されますが、よろしいですか?」と表示する
  • 前回保存時から投稿に変更があった場合のみ、ダイアログを表示する (未編集時にダイアログが表示されるのは煩わしいため)
  • 「保存する」を押して保存された状態であれば、ダイアログを表示しない

環境情報

  • Next.js: 11.0.1
  • TypeScript: 4.3.5
  • Bootstrap: 5.0.2

実装上の課題

上記を実現するにあたり、2つ課題がありました。

  1. Next.jsではbeforeunloadイベントでブラウザバックを検知できない
    beforeunloadイベントは、画面遷移が要求されページがアンロードされるときに発動されますが、Next.jsでのページ遷移はURLに対応したコンポーネントに差し替えているだけですので、ページがunloadされません。
    したがって、今回はこちらのイベントは使いません。 SPAでなければ、以下のサイトの方法で実現できます。
    フォームの2大誤操作「閉じる・戻る」での離脱を減らす確認ダイアログを実装しよう/15か条の10

  2. ブラウザバック(戻る)による遷移を止めるのに工夫が必要 ※後述します

実装方法

結論、以下の2つの方法で、実装することができました。

方法1: addEventListenerでpopstateイベントを追加する

popstateイベントはブラウザの「戻る」や「進む」を押下した際に発火されます。

popstate イベントは、同じ文書の2つの履歴項目の間で、アクティブな履歴項目が変わるたびにウィンドウに発行されます。
WindowEventHandlers.onpopstate

コード例

pages/posts/new.tsx

import React, { useState, useEffect } from 'react';
import { NextPage } from 'next';

export const NewPost: NextPage = () => {
  const [text, setText] = useState('');
  // 編集中かどうかをstateで管理
  const [isEdited, setIsEdited] = useState(false);

  const handlePopstate = () => {
    const isDiscardedOK = confirm(
      '保存されていないデータは削除されますが、よろしいですか?',
    );
    if (isDiscardedOK) {
      // OKの場合、historyAPIで戻るを実行します。
      window.history.back();
      setIsEdited(false);
    }
    // キャンセルの場合、 ダミー履歴を挿入して「戻る」を1回分吸収できる状態にする
    history.pushState(null, '', null);
  };

  useEffect(() => {
    // 編集中になったとき、現在のページを履歴に挿入し、handlePopstateをイベント登録
    if (isEdited) {
      // ダミー履歴を挿入して「戻る」を1回分吸収できる状態にする
      history.pushState(null, '', null);
      window.addEventListener('popstate', handlePopstate, false);
    }
    // 他のページに影響しないようclear
    return () => {
      window.removeEventListener('popstate', handlePopstate, false);
    };
  }, [isEdited]);

  return (
    <div
      className='d-flex justify-content-center'
      style={{ marginTop: '15vh' }}
    >
      <div>
        <h1>投稿画面</h1>
        <form>
          <textarea
            rows={10}
            cols={60}
            value={text}
            onChange={e => {
              setText(e.target.value);
              setIsEdited(true);
            }}
          ></textarea>
        </form>
        <div className='d-flex justify-content-end'>
          <button
            className='btn btn-primary mt-3 me-3'
            // 保存したときは、編集中フラグをfalseにする
            onClick={() => setIsEdited(false)}
          >
            保存する
          </button>
          <button className='btn btn-primary mt-3'>投稿する</button>
        </div>
      </div>
    </div>
  );
};

export default NewPost;

実際に入力欄に文字を打った後に「戻る」を押してみてください。確認ダイアログが表示されるはずです。 入力前と保存をした後であれば確認ダイアログは表示されません。

捕捉説明
history.pushState(null, '', null);で現在のページと同じ履歴を挿入することができます。パラメータは前から、state(状態オブジェクト), title, url になっており、urlにnullを指定することで現在のページの履歴が挿入されます。参考: History.pushState()

現在のページと同じ履歴、いわばダミーの履歴を挿入する理由は、「戻る」によるURLの変更を制御できないためです。

たとえば、以下のようにコメントアウトしておきます。

    ...
    if (isEdited) {
      // history.pushState(null, '', null);
      window.addEventListener('popstate', handlePopstate, false);
    }
    ...

投稿画面のURLは/posts/new、ブラウザバックで戻るURLが/postsだとします。

投稿画面のURL
投稿画面のURL

入力した後に「戻る」を押すと、確認ダイアログが出ますが、URLが/postsに戻ってしまいます。

「戻る」を押した後
「戻る」を押した後

という具合に「戻る」によるURLの変更を制御できないことがわかります。 これによって、URLと実際にレンダリングされている画面にズレが生じてしまいます。

ダミーの履歴を挿入することでこれを解決します。

posts/posts/new
となっていた履歴の間にダミー履歴を挿入して、
posts/posts/newposts/new
にします。

この状態であれば「戻る」を一度押しても、posts/newにいる状態を維持できます。

実は編集後に保存を押すと、確認ダイアログは出なくなるのですが、「戻る」を二度押さないと戻ることができません。これは上記のダミー履歴が蓄積されていることによるものです。

ユーザが編集中のデータを失わないという本質的な要件は満たしており、実装コストが見合わなそうだったので、今回はダミー履歴が蓄積されてしまう事象は解決しませんでした。

方法2: Router.beforePopStateを使う

Router.beforePopStateは、Routerのメソッドで以下の説明にあるようにpopstateイベントを捕捉してイベントを発動するために利用しています。

Router.beforePopState 場合によっては(例えば、カスタムサーバーを使用する場合)、popstate をリッスンして、ルーターが動作する前に何かしたいということがあります。
next/router

コード例

pages/posts/new.tsx

import React, { useState, useEffect } from 'react';
import { NextPage } from 'next';
import { useRouter } from 'next/router';

export const NewPost: NextPage = () => {
  const [text, setText] = useState('');
  // 編集中かどうかをstateで管理
  const [isEdited, setIsEdited] = useState(false);

  const router = useRouter();

  const setHandlePopstate = () => {
    // ダミーの履歴を挿入し、ブラウザバックを1回分吸収する
    history.pushState(null, '', null);
    router.beforePopState(() => {
      const isDiscardedOK = confirm(
        '保存されていないデータは削除されますが、よろしいですか?',
      );
      // OKの場合、historyAPIで戻るを実行します。
      if (isDiscardedOK) {
        setIsEdited(false);
        router.back();
        return true;
      }
      // キャンセルの場合、 ダミー履歴を挿入して「戻る」を1回分吸収できる状態にする
      history.pushState(null, '', null);
      return false;
    });
  };

  // trueをreturnしてページ遷移が正常に動作するように戻す
  const clearHandlePopstate = () => {
    router.beforePopState(() => true);
  };

  useEffect(() => {
    // 編集中になったとき、現在のページを履歴に挿入し、handlePopstateをイベント登録
    if (isEdited) {
      setHandlePopstate();
    } else {
      clearHandlePopstate();
    }
    // 他のページに影響しないようclear
    return () => clearHandlePopstate();
  }, [isEdited]);

  // 以下は方法1と同じ
  return (
    <div
      className='d-flex justify-content-center'
      style={{ marginTop: '15vh' }}
    >
      <div>
        <h1>投稿画面</h1>
        <form>
          <textarea
            rows={10}
            cols={60}
            value={text}
            onChange={e => {
              setText(e.target.value);
              setIsEdited(true);
            }}
          ></textarea>
        </form>
        <div className='d-flex justify-content-end'>
          <button
            className='btn btn-primary mt-3 me-3'
            // 保存したときは、編集中フラグをfalseにする
            onClick={() => setIsEdited(false)}
          >
            保存する
          </button>
          <button className='btn btn-primary mt-3'>投稿する</button>
        </div>
      </div>
    </div>
  );
};

export default NewPost;

挙動は方法1と同じです。

捕捉説明
useEffect内で、return () => clearHandlePopstate();してあげることが大切です。Routerに紐づけるイベントなのでclearしないと関係のないページで確認ダイアログが表示される可能性があります。

また、確認ダイアログを無効化するために保存によってisEditedfalseになったときもclearHandlePopstateを実行しています。

おわりに

方法1は主にブラウザの機能で実現していて、方法2は主にNext.jsの機能で実現しています。

ブラウザ機能の「戻る」が絡む実装なので、ブラウザ機能に寄せたいという方は方法1を、Next.js上で実装しているのでNext.jsに寄せたいという方は方法2を使うと良いと思います。

やや強引に実装しましたが、そもそも簡単に実装できるブラウザAPIが提供されていればよいですよね。

調べたところ「戻る」が効かないサイトを作ることができてしまうため、UXの観点から提供されていないようです。

ブラウザバックの制御については社内のエンジニアも毎回面倒くさいと嘆いていました。

この記事を読んで、ブラウザバックを制御しようとしている方の嘆きがなくなれば幸いです。

【Python】AtcoderでTLEになる計算量の閾値を調べてみた 【PyPy】

目的

最近趣味でAtcoderという競技プログラミングサイトで活動をしているのですが、 提出するコードの計算量をかなりざっくりと見積もってしまっていて、提出した結果、指定された実行時間を超えてしまいTLE(Time Limit Exceeded)になってしまうことが多いので、 どの程度の計算量であればTLEにならずに実行できるのか調査してみました

調べてみた

環境

言語: PyPy 実行時間制限: 2 sec メモリ制限: 1024 MB

10 ** 7回ループ回してみる

for _ in range(10 ** 7):
    a = 'a'

N = int(input())
print(((N - 1) // 100) + 1)

f:id:keimaeda0817:20211129180731p:plain 10**7までは大丈夫そうですね、ただ487msかかっているので10**8ループだとTLEになりそうです

10 ** 8回ループにしてみる

for _ in range(10 ** 8):
    a = 'a'

N = int(input())
print(((N - 1) // 100) + 1)

f:id:keimaeda0817:20211129181036p:plain TLEになりました。今回ループ内で行っている処理がかなり簡単なものなので、どのような処理でも10**8以上のループはTLEになりそうです

結果

10 ** 7回のループまでならTLEにならずに実行できることが分かりました。 C問題などを解いていても体感10 ** 7くらいまでなら通るので、体感通りでした。

getStaticPathsやgetStaticProps、getServerSidePropsなどが突然消える

こんにちは!エンジニアの川野です。

台風が過ぎ去って夏本番の暑さですね。怖い話でも聞いて涼みたいたいということで、 今回はVSCodeで表題の関数が突然消えるという恐怖体験について書きました。

心霊現象

Next.jsで開発をしているとき、下の画像のようなエラーに度々遭遇しました。

度々遭遇するエラー
度々遭遇するエラー

Server Error
Error: getStaticPaths was added without a getStaticProps in /posts/[id]. Without getStaticProps, getStaticPaths does nothing

This error happened while generating the page. Any console logs will be displayed in the terminal window.

直接の原因はエラーメッセージのとおり、getStaticPathsが書かれているのにgetStaticPropsが書かれていないというものでした。

ただ、実装し忘れてそうなったのではなく、実装してあったgetStaticPropsが突然、何者かによってファイルから消されてしまったのです。getServerSidePropsについても同様に消されてしまうことがありました。

消えたコードを履歴から戻せばエラー自体は解決するのですが、毎回30秒ほど時間を奪われるので原因をしっかり調査しました。

環境情報

  • VSCode: 1.58.2
  • TypeScript: 4.2.3
  • ESLint: 7.23.0

原因

その何者かは、VSCodeの到達できないコードの削除機能とsettings.jsonの設定値でした。

VSCodeにはreturnthrowの後方の、プログラム的に到達不可能なコードを削除する機能があります。

たとえば、以下のようにコードを書き、returnの後方のコードで cmd + .を押すと、Remove unreachable code とコマンドが出てきます。

到達不可能なコード
到達不可能なコード

実行すればもちろん消えてしまいます。

Remove unreachable code 実行後
Remove unreachable code 実行後

そしてこの機能はsettings.jsonで下記の設定をしていると、save時に発動するようになってしまいます。

settings.json

{
    "editor.codeActionsOnSave": {
        "source.fixAll": true
    }
}

解決策

もちろんsource.fixAllfalseにすれば突然消えるということはなくなるのですが、それではESLintのフォーマットが動作しなくなってしまいます。

以下の設定にすればESLintのフォーマットだけ動作するようになりました。

settings.json

{
    "editor.codeActionsOnSave": {
        "source.fixAll.eslint": true
    }
}

参考:Valid "unreachable" code is being aggressively removed as a result of syntax errors when allowUnreachableCode: false #109530

フォーマットされなくても、到達不可能コードはESLintが検知してくれます。
disallow unreachable code after return, throw, continue, and break statements (no-unreachable)

到達不可能コードはESLintが検知
到達不可能コードはESLintが検知

無事、解決できました!

RIT卒業します

初めまして。2020/11からRITでインターンとして参画させていただいた千葉と申します。 今回はRIT卒業にあたり、振り返りの記事を書かせていただきます。

インターン参画前

僕がRITにインターンとして参画する前は、実務経験がなくほぼ未経験者といった状態でした。一応プログラミングスクールで勤務していたので最低限の基礎は習得していましたが、実際のサービス開発は経験したことはありません。(社内のみで使用する小さなアプリケーションは開発したことはありますが)

インターンへの応募理由

プログラミングスクールで実務を学べる機会(開発に進む機会)もありましたが、会社の開発人数や予算の都合といった理由から、その道に進むことが難しい状況でした。そこでインターンとして実務経験を積み、リアルな開発現場というものを味わってみたかったというのがインターンへの応募理由です。そこで偶然、Wantedly経由でRITに出会い参画させていただくことになりました。

インターンの良かった点

未経験の言語に携われたこと

これまで触ったことのなかったフロントエンドの言語に携わらせていただきました。具体的にはReactとTypeScriptで、この開発を機にフロント周りの学習を始め、徐々に知識も身に付いてきたのかなと思います。

実際の案件に携われたこと

始めの1~2ヶ月は自社サービスの改修に携わりましたが、その後は実際の案件に携わらせていただきました。自身の転職事情でリリースまで携わることができなかったことに申し訳なさと後悔がありますが、1つのサービスを1から携われたことは自分にとって大きな成長機会になりました。

好きな時間で開発ができる

案件の納期等はありますが、基本的には自分の都合で開発を進めることができました。僕の場合は本業もあったので、平日夜や土日にメインで作業を進めていました。

インターン生でも気軽に発言できる

僕含め他のインターン生を見ていても、上下関係があまりなく、誰でも気軽に発言できる雰囲気があると感じました。インターン生と正社員の間には壁ができがちですが(少なくとも本業で働いていた会社ではありました)、交流会を開いていただいたり、MTGに参加させていただいたりと、できるだけこういった壁をなくす工夫がされていてとてもありがたかったです。

後悔していること

  • 携わっているプロジェクトを完遂できなかったこと
  • ReactとTypeScriptの案件に携わる期間が短かったこと

僕が原因で出来なかったことなのですが、後悔している点を挙げるとすると上記2つになります。携わっているプロジェクトがなくならない限り、おそらくは基本的に最後まで携わることができると思います、多分。

今後の進路

今後はヘルステックの企業でRailsエンジニアとして勤務予定です。転職活動の際にReactとTypeScriptを用いたポートフォリオを作成しましたが、これはRITで開発業務に携わらせていただく機会がなければ作成できなかったと思います。今後も引き続きサーバーサイドだけでなくフロントのスキルも高めていきたいと考えています。

最後に

RITではエンジニアを募集しています。 興味のある方はぜひ覗いてみてください。 https://www.wantedly.com/companies/rit-inc/projects

GCPでドメイン買えるようになってた(まだプレビュー)

GCPでサービスを構築するときにドメイン(Google Domains)とメール(SendGrid)周りだけ外部サービス使うのが面倒でなかなかクライアントに提案しづらかった福田です。

いつもの外部サービスの設定面倒だなーと思ってなんとなくdomainで検索してたら謎のCloud Domainsというサービスがあったので試してみました。

f:id:rit-inc:20201122171341p:plain

結論から言うと、AWSのRoute 53みたいにドメインの購入からDNSへの紐付けまでGCP上で一気通貫で完結するサービスでした。すごい嬉しい。

2020/11/25 追記

Cloud Runからでも買えるようになってたみたい

f:id:rit-inc:20201125230524j:plain

作業ログ

f:id:rit-inc:20201122171433p:plain

ドメインを登録ってリンクが気になる

まさかRoute53みたいにGCPだけでドメインの購入からDNSの設定まで完結するようになる?

もしそうなら超嬉しい

f:id:rit-inc:20201122171458p:plain

買えそうなのでカートに入れて続行

f:id:rit-inc:20201122171521p:plain

そのままCloud DNSまでつながってくれるっぽい

別のラジオボタンクリックしたら"Cloud DNSを使用する"に戻せなくなるので注意(1敗)

DNS APIを有効にしてから公開DNSゾーンを作れって書いてあるのに作らずに進むと最後に登録に失敗するので注意(1敗)

f:id:rit-inc:20201122171539p:plain

Cloud DNSにゾーン作ってから続行

f:id:rit-inc:20201122171638p:plain

whoisは保護しとく

f:id:rit-inc:20201122171707p:plain

情報を登録

f:id:rit-inc:20201122171600p:plain

できた!

さいごに

新規サービス立ち上げのためにAWSGCPでインフラを構築することが多いんですが、これでGCPで構築するのが更に楽になりますね。

特にクライアントによっては契約が一本増えるだけで手続きが複雑になったりもするので、その点でも嬉しいです。

RITでは、新しいサービスを活用した効率的なプロダクト開発でサービスの0~1をリードするエンジニアを募集しています!

herp.careers

コーポレートサイトのNext.js 10対応ついでにパフォーマンス改善してみた

f:id:rit-inc:20201107133327p:plain

最近京都に引っ越した福田です。 全然更新できてないですがエンジニアも増えてきたので頻度上げたいとは思ってます。

Next.js 10リリースされましたね

next/imageが出てきたりhrefasが要らなくなったりi18n系の新機能だったり、すぐにでも使ってみたい機能が色々ありましたね。 RITのコーポレートサイトではNext.js使ってるんですが、特に画像の最適化をサボってたせいでLighthouseのスコアがよろしくなくて、丁度いい機会だったので主にnext/image使うためにバージョンアップしてみました。

変更点

インフラ構成の変更

旧構成

github actions + firebase
旧構成

図にするほどのものでもないですがGitHub Actionsでbuildとexportしたファイルをfirebase hostingにデプロイしてます。

新構成

github actions + firebase + cloud run
新構成

Cloud Runが追加されてますね。 これは微妙にハマったポイントでもあるんですが、現状next/imageSSR必須っぽいのでnextを動かすサーバが必要になります(違ったらごめんなさい)。 なので、firebaseでキャッシュされてるリソースはfirebaseから返しつつ、キャッシュされてないリソースは裏のCloud Runにリクエスト投げてビルドしてもらうような構成に変更しました。 実際のfirebase.jsonは↓みたいな感じ(rewritesのところがCloud Runにリクエスト投げる設定)。

{
  "hosting": {
    "public": "public",
    "ignore": [
      "firebase.json",
      "**/.*",
      "**/node_modules/**"
    ],
    "rewrites": [
        {
            "source": "**",
            "run": {
                "serviceId": "<Cloud Runのサービス名>",
                "region": "asia-northeast1"
            }
        }
    ]
  }
}

Cloud Runはリクエストがなければ0までスケールダウンしてくれるので、適切にキャッシュしておけばCloud Runの費用はほぼかからないはず。 この"適切にキャッシュ"というのが厄介なんですが、firebaseはCloud RunからのレスポンスのpublicなCache-Controlヘッダを見てfirebaseへのキャッシュを行う(参考)ので、next.jsのレスポンスでCache-Controlヘッダを返してやる必要があります。 特にキャッシュしちゃいけないリソースもないので、ここでは適当にすべてのパスへのリクエストで1日キャッシュするように設定しておきました。 nextのheader周りあんまりちゃんと理解してないのでもっといい設定方法があるかも。

module.exports = {
    async headers() {
        return [
            {
                source: '/',
                headers: [
                    {
                        key: 'Cache-Control',
                        value: 'public, max-age=86400',
                    },
                ],
            },
            {
                source: '/:path*',
                headers: [
                    {
                        key: 'Cache-Control',
                        value: 'public, max-age=86400',
                    },
                ],
            },
        ];
    },
};

next/imageの導入

一番面倒だったのはlayout指定しないと必須になるwidthとheightの指定です。 ただ、これはそもそもnext/image使わずただのimgタグだったとしてもUX的にちゃんとしといたほうがいいので頑張って指定しましょう。 他はimgタグと同じ感覚で大丈夫でした。

<Image
    src='/static/img/top/service.jpg'
    alt='service'
    width={612}
    height={510}
/>

レスポンスの画像がwebpになってる
webp確認

ちゃんとwebpになってますね。

結果

(Next.js関係なくページ遷移時のアニメーション消したのも影響してますが許してください)

変更前

Performanceが低い
Lighthouse結果

よろしくない数字ですね。

変更後

Performanceが91に改善
Lighthouse結果

だいぶ改善しました。 画像の最適化という面倒な問題をnext/image使うだけでいい感じにやってくれるのは便利ですね。

さいごに

最初はnext/image使って高速化するだけのつもりだったんですが、Lighthouseちゃんと見てみたらNext関係ない部分で最適化できる箇所とかも割とあったので、適宜パフォーマンスチェックするのは大事ですね。

RITでは、新しい技術をキャッチアップしてユーザに最適なパフォーマンスで価値を提供できるエンジニアを募集しています!

herp.careers

RITのインターンを卒業します

はじめまして.RITでエンジニアのインターンとして働いている石川です. ちょうど参画してから2年目 & 就職でRITとお別れなので,感謝の意を込めて振り返り記事を書かせていただきます.

要約

  • 入る前
    • 自分は知識だけで経験不足の役に立たない人間
    • ITベンチャーでレベルアップしたい
  • 予想通り
    • 大きい裁量
    • メンバーの距離が近い
  • ギャップ
    • 賑やか
    • ギスギスしてない
  • 経験したプロジェクト
    • JWBF:顧客と開発のバランス
    • VOV:ログ
    • その他:いろいろ
  • これから

入る前

RITで働き始める前は,知識だけで経験不足の役に立たない人間でした.

情報系の大学に所属してたり,応用情報技術者の資格を所持してたりと,最低限のITやプログラミングの知識・能力はありました. 一方で,自分で何かちゃんとしたサービスを作ったこともなく,得た知識が業務でどのように役立つのか分かりませんでした.

このままでは社会の役に立てないので自分の経験を積みたいと思い,以前のバイトをやめてITベンチャーのRITに入りました.

予想通りだったこと

裁量が大きい

メンバーが少ないので一人ひとりに大きい裁量が与えられます.(僕がベンチャーを選んだ理由) インターンの身分でもクラウドサービスのかなり重要な権限も付与してもらったりしました. また,企画やミーティングなどでも自分の意見を尊重してもらい,方針を左右したりも. 裁量が大きいと色んなことができて楽しいです.

メンバーの距離が近い

また,メンバー間の距離が近いです. 新年会や忘年会だけでなくBBQしたりしますし,プライベートで遊んだりしています.

人生ゲームの極辛を遊んだのはいい思い出です. 救済システム(不運からプレイヤーを守ってくれる)を認識しておらず,負債者続出. 30万くらい借金抱えた人もいたり.

ギャップ

賑やか

10人近くのメンバーがオフィスにいてわいわいしてます. また,コンサルタントの多くは他の場所にいるのですが,その方々がオフィスに来たときは更に賑やかで楽しいです.

とか思って書いてたら,新型コロナで安全をとってみんなリモートに🙄(2020年2月時点)

ギスギスしてない

スタートアップと聞くと,ありがちなのが距離が近いゆえにギスギスした関係. ドラマだとメンバー間で問題発生してチーム決裂みたいなことがよくあります.

RITでは,メンバー皆がより良いサービスを提供しようと従事していますが,居心地が悪いと感じたことや悪そうな人はいないです. プロジェクトに問題は付き物ですが,発生しても人ではなく仕組みに原因があると考え,問題の発生しない仕組みを議論して構築・改善しているからだと思います.

あと,地味ですが労働環境が整っていることがこれを支えているのかなと思ったり. オフィスは広く,大きいモニターや人間工学に基づいた座り心地の良いイスが配備されてますし,コーヒーメーカーや軽食も置いてあります. さらにスタートアップにありがちなブラックに深夜遅くまで仕事して体を壊すなんてことはないです(多分?).

これまで

二年間で様々なプロジェクトに携わってきました. 紹介するには多すぎるので2つだけ取り上げます.

JWBF

jwbf.gr.jp

一つ目は日本車いすバスケットボール連盟のホームページです.主に,フロントエンドとWebサーバーの開発に携わらせていただきました.

主に用いた技術は次の通りです.

他の記事でも言っている通り,RITではフロントエンドはReactを用いることが多いです.ただ,このプロジェクトではVueにチャレンジしています.書き方などを覚えれば特に問題ありませんでした.ただ,VSCodeのVueのショートカットが一部非対応なため少し煩わしかったです.それ以外ではVSCode最高!!

このプロジェクトではモデルの持つ情報量が大きく,モデル間の依存関係が複雑でした. また,顧客(サイト管理者)視点では一つの画面に関連する項目を表示・編集できた方が嬉しいので,複数のモデルを管理できるようにしていました. この状態管理がハードで, Aの更新画面でBを更新→Bの更新が完了したら→Cを選択してもらい→Aの情報を入力して→Aの更新を完了する みたいな処理がたくさん存在していました.

顧客としては一つの画面に色々な機能があったほうが使いやすいことも多いですが,開発側としては一つひとつがシンプルな方が楽だと実感しました.(そもそもそんなに大変だと予測できてなかった😵)

顧客と開発のバランスは大事.このバランス調整こそSEの力の見せ所だと思うので,次の職場で経験していきたいポイントです.

vov

2つ目は,ビンテージアイテムのWikiプラットフォームのvovです.ユーザは読者と編集者がいて,主に読者側の開発を行いました.また,ログの設計と基盤の構築も行いました.

vov-vintage.com

主に用いた技術は次の通りです.

Webアプリ自体はいつもの構成(Next.jsだけ未経験)で大きな支障はありませんでした.

一方,ログ関連はほぼ未経験で,BigQueryは少し触ったこと有るけど…というレベル.

Analytics,DataPortalの使い方を学ぶところから始め,KGIツリーやログの完成予想図を顧客に見てもらったり,AnalyticsやFluentd,BigQueryによりログを蓄積させDataPortalで可視化したりと いろいろやることがありました.

知らないことばかりで大変でした. しかし,改めて振り返ってみると構築や思考手順などが残っているので,再現性のある貴重な経験だったと思います.

その他

JWBFとVOV以外のプロジェクトでもWebアプリを中心に開発していました. ReactやRailsなどのいつものWeb系のフレームワークはもちろんのこと,AWSのBeanstalkやECS を用いたインフラ構築も経験させていただきました.(おかげさまでAWSのSAAに合格したり) それ以外にも,案件・顧客に合わせて担当領域を変えていたので,それ以外にも全文検索エンジン,CI・CD,などなど,挙げればキリが無く,本当に色んな分野のスキルを身につけることができました.

また,技術以外にも,チーム開発における流れや大切なことを学ぶことができましたし,企画や立案に携わることもできました.

これから

目標としていたITの業務経験を積むことができました. 中でも,一つの技術領域にとらわれず幅広い体験をできたことは本当に感謝しています.

次の職場ではSEとして働きます.部署やプロジェクトは未定ですが,RITでの経験を活かせばどんなプロジェクトでも活躍できると信じてます.特に,調整力に集中して会得しつつ,技術面で力を発揮していきたいと思います.