投稿者: コイアイちゃん

  • Django × AWS RDS でバックエンドを構築する手順

    Django × AWS RDS でバックエンドを構築する手順

    Flutterで単語帳アプリを構築しようと思い立ち、バックエンドを Django + AWS RDS(PostgreSQL)に選定しました。今回はこの手順をメモがてら記事に残そうと思います。

    環境

    • M1 MacBook Air

    使用技術スタック

    • Django 4.x(Python 3.13)
    • AWS RDS(PostgreSQL)
    • psycopg2-binary(PostgreSQL ドライバ)
    • 環境変数管理:python-dotenv
    • API:Django REST Framework

    🛠 構築手順

    1. Django プロジェクトの準備

    仮想環境を立ち上げ、django プロジェクトを作成しました。今回は単語帳アプリなのでvocab という名前にしましたが、これを参考にしている方は好きなプロジェクト名にしてください。

    python -m venv venv
    source venv/bin/activate
    pip install django djangorestframework psycopg2-binary python-dotenv
    django-admin startproject config .
    python manage.py startapp vocab

    2. .env ファイルの作成(RDS 接続情報)

    プロジェクトのルートディレクトリに .env ファイルを作成し、AWS RDS で設定した際に表示される以下情報を.env にまとめました。

    .gitignore に .env を追加しておけば、リモートでの管理ができます。

    DB_HOST=your-rds-endpoint.ap-northeast-1.rds.amazonaws.com
    DB_NAME=vocab
    DB_USER=yourusername
    DB_PASS=yourpassword

    3. settings.py に DB接続を設

    先ほど作った.env を読み込ませます。PORTはデフォルトで5432なので直接書きました。

    import os
    from dotenv import load_dotenv
    
    load_dotenv()
    
    DATABASES = {
        'default': {
            'ENGINE': 'django.db.backends.postgresql',
            'NAME': os.getenv('DB_NAME'),
            'USER': os.getenv('DB_USER'),
            'PASSWORD': os.getenv('DB_PASS'),
            'HOST': os.getenv('DB_HOST'),
            'PORT': '5432',
            'OPTIONS': {
                'sslmode': 'require',
            }
        }
    }

    4. AWS RDS(PostgreSQL)インスタンスの構築

    AWS RDS のコンソールにWebからアクセスして、作成したRDS の設定を変更します。

    • パブリックアクセス:はい
    • セキュリティグループで 5432 ポートを開放
    • ソース:自分のIP/32(ターミナルでcurl -4 ifconfig.me で確認)

    5. セキュリティグループを RDS に割り当て

    AWS Console → RDS → データベース → 「変更」→ セキュリティグループを追加


    6. RDS に接続してデータベースを作成

    vocab という名前のデータベースを作成しました。psqlコマンドを使用するので入ってない人はインストールしてください。

    brew install postgresql
    psql "host=your-rds-endpoint port=5432 user=yourusername dbname=postgres sslmode=require"
    CREATE DATABASE vocab;

    7. Django モデル定義とマイグレーション

    例:vocab/models.py

    from django.db import models
    
    class Vocabulary(models.Model):
        word = models.CharField(max_length=100)
        meaning = models.TextField()
        created_at = models.DateTimeField(auto_now_add=True)

    マイグレーション実行:

    python manage.py makemigrations vocab
    python manage.py migrate

    8. Django REST Framework でAPI構築

    vocab/views.py

    from rest_framework import viewsets
    from .models import Vocabulary
    from .serializers import VocabularySerializer
    
    class VocabularyViewSet(viewsets.ModelViewSet):
        queryset = Vocabulary.objects.all()
        serializer_class = VocabularySerializer

    vocab/serializers.py

    from rest_framework import serializers
    from .models import Vocabulary
    
    class VocabularySerializer(serializers.ModelSerializer):
        class Meta:
            model = Vocabulary
            fields = '__all__'

    config/urls.py

    from django.urls import path, include
    from rest_framework.routers import DefaultRouter
    from vocab.views import VocabularyViewSet
    from django.http import JsonResponse
    
    router = DefaultRouter()
    router.register(r'vocab', VocabularyViewSet)
    
    def root_view(request):
        return JsonResponse({"message": "Vocab API is running!"})
    
    urlpatterns = [
        path('', root_view),
        path('api/', include(router.urls)),
        path('admin/', include('django.contrib.admin.urls')),
    ]

    9. 開発サーバー起動・確認

    python manage.py runserver

    確認:

    • http://127.0.0.1:8000/ → 簡易JSON

    • http://127.0.0.1:8000/api/vocab/ → API 動作確認

    ✅ まとめ

    Django × AWS RDS の連携は、セキュリティグループやパブリックアクセスの設定など、最初の接続までが一番の壁でしたが、そこを乗り越えれば快適にクラウドDBで開発が進められます。


    ✍️ 補足(記事に入れてもOK)

    • .env に機密情報を書くので .gitignore で Git に上げないこと

    • RDS の接続テストは psql の sslmode=require を忘れずに

    • セキュリティグループのソースIPに注意(固定IPでない場合は都度更新)

  • Amplify Data Manager から登録できない時

    Amplify Data Manager から登録できない時

    Amplify Studio Data Manager でコンテンツ登録しようとしたところ、

    IAM is required as an auth provider to use content management capabilities. To automatically add IAM and enable content management, navigate to 'Data' and select 'Save and Deploy'.

    と表示されました。

    この画面で Go to Data Model をクリックして遷移した先で, Save and Deploy をしたところ、

    An error occurred while processing your request: Deployment failed because your app backend contains hosting. Amplify Studio only supports API, Auth, and Storage deployments. Please use the Amplify CLI to deploy updates.

    となってしまいました。

    解決方法

    私の場合 hosting を試しに使用していたのが問題でした。

    amplify remove hosting

    再度バックエンドをプッシュ

    amplify push

    その後 Amplify Studio の Data に移動し、 Save and Deploy をしたところ、deployが成功しました!

    この後 content / Data Manager に戻って無事amplify studio からデータの操作ができるのを確認できました!

  • Amplify Studio を使う

    Amplify Studio を使う

    この記事ではAmplify Studio の使い方について軽めに解説します。

    amplify を使ってデプロイしたアプリに管理画面を作ろうとしたところ、古い記事を参照したため Amplify admin UI という単語が出てきたのですが、Amplify Studio に統合されたようです。

    Amplify Studio を開く

    始める前にGithubで新しくリポジトリを作っておくと便利です。

    amplify cli を使ってプロジェクトを準備する方法は下の記事をご覧ください。

    AWS console から Amplify を開きます。

    開いて、まだ自分のアプリをデプロイしていない場合は指示に従ってデプロイをしましょう。

    デプロイに成功すると、Amplify console から amplify studio の設定を開けるようになります。

    amplify console はターミナル上で以下コマンドを実行すると表示できます。 ⚠️検索バーでamplify と調べて出てくるページとは違うので注意

    amplify console

    console が開いたら Go to Amplify Studio to enable をクリックします。

    amplify studio を有効にします。 のトグルスイッチをオンにしましょう。

    読み込みが終わると上のような画面になります。Backend Environments のURLをクリックすると、Amplify Studio に移動できます。

    Login with AWS account のボタンを押して遷移しましょう。

    このような画面に遷移するはずです!

    ここまできたら、サイドバーからUI Library → sync with Figma など話題の機能が使えます!

    お疲れ様でした。

  • Amplify React デプロイまで

    この記事ではAWS Amplify を使ってReacgtプロジェクトをビルドするまでの手順をメモとして残しています。

    やり方

    Amplify の初期設定

    Amplify cli をインストールします。

    npm install -g @aws-amplify/cli

    次にAWS cli の認証情報を設定します。これはコンソールからIAMユーザーの作成と権限の付与が事前に必要です。

    amplify configure

    Amplify プロジェクトの初期化

    用意してあるReactプロジェクトのディレクトリに移動して、次のコマンドを実行します。

    cd your-react-project
    amplify init

    対話形式で色々と聞かれるので順に答えていきます。

    APIの追加

    amplify add api

    このように必要なパッケージをaddします。

    変更をpush

    全て終わったらpushしましょう。

    amplify push

    デプロイ

    デプロイ方法は2つあり、一つはこのままCLIで行う方法です。もう一つはAWS Amplify console でWeb上でデプロイをする方法です。

    まずはCLIを使った方法です。

    amplify add hosting
    amplify publish

    これでOKです。

    もう一つのWeb上でのデプロイはamplify console に移動して、GitHubとリポジトリを連携していけばデプロイできます。

    参考

    参考にさせていただいた動画のリンクです。

  • 25年3月現在最善のReactプロジェクトの作り方

    25年3月現在最善のReactプロジェクトの作り方

    Vite(ヴィート)を使おう。Viteは、高速なフロントエンド開発環境を提供するビルドツール です。https://vite.dev/


    create-react-app は古い方法になってしまいました。create-react-app は、React チームが開発していた公式ツールですが、2023 年以降、ほぼ開発が停止 しました。

    Vite は ESモジュール(ESM)とホットリロード(HMR) を使って、開発時のリビルドを 超高速化 しています。また、
    デフォルト設定で十分使いやすく、webpack.config.js のような面倒な設定が不要です。

    やり方

    Node.js をインストールしましょう。

    以下コマンドでプロジェクトを作成できます。my-react-app は好きな名前を入れてください。

    npm create vite@latest my-react-app -- --template react

    依存関係をインストールしましょう

    cd my-react-app
    npm install

    開発サーバーを起動させてみましょう。

    npm run dev

    コマンドラインに出てきたlocalhost を立ち上げたら完了です!

  • Next.js で Recoil を使いたい TypeError: Cannot destructure property ‘ReactCurrentDispatcher’ of ‘react__WEBPACK_IMPORTED_MODULE_0___default(…).__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED’ as it is undefined.

    Next.js で Recoil を使いたい TypeError: Cannot destructure property ‘ReactCurrentDispatcher’ of ‘react__WEBPACK_IMPORTED_MODULE_0___default(…).__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED’ as it is undefined.

    Recoil を使ったプロジェクトをNext.js に移行しようとしたところ以下のようなエラーが出てしましました。

    TypeError: Cannot destructure property 'ReactCurrentDispatcher' of 'react__WEBPACK_IMPORTED_MODULE_0___default(...).__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED' as it is undefined.

    この記事はこれを解決するための記事です。

    やり方

    ページまたはコンポーネントごと、SSRをオフにする。

    今までRecoilを使っていたページを下記のようにラップしてしまいます。

    私の場合の例を出しますと。元々はdiagnosis.tsx にrecoil を含む動作を書いていたたのですが、
    pages/diagnosis.tsx を直接書くのではなく、diagnosis.page.tsx に全てコードを移行し、
    diagnosis.tsx の中で動的にインポートしました。

    // pages/diagnosis.tsx
    import dynamic from 'next/dynamic';
    
    // diagnosis.page.tsx をクライアントサイドのみで読み込む
    const DiagnosisPageComponent = dynamic(() => import('../diagnosis.page'), {
      ssr: false,
    });
    
    export default function DiagnosisPage() {
      return <DiagnosisPageComponent />;
    }
    元々のRecoilを含むコード

    diagnosis.page.tsx には実際の実装(Recoil フックを呼び出す部分)を記述します。こうすると、サーバーサイドでは空っぽのコンポーネントしか返さないため、エラーを回避できました。

    React と ReactDOM のバージョンを一致させる

    以上で解決できない場合はReact と ReactDOM のバージョンを見直してみてください。どちらも同じバージョン出ないと不具合が出る場合があります。

  • Next.js SSG を設定する

    Next.js SSG を設定する

    SEO対策のためにSSGを導入しようと思い、その方法をいかにメモしました。
    よければ読んでいってください!

    SSGとは?

    SSG(Static Site Generation)は、Next.js の データ取得(データフェッチ)戦略の1つで、ビルド時に HTML ファイルを生成し、それを配信する仕組みです。

    手順

    next.config.js の設定

    以下のコードでnext.js は next export を実行すると、静的HTML/CSS/JS ファイルを生成します。

    module.exports = {
      output: "export", // 静的サイト (SSG) を生成する
      trailingSlash: true, // 各ページに `/` をつける (オプション)
    };

    パッケージのインストール

    yarn install  # または npm install
    yarn global add serve  # または npm install -g serve

    next.js のビルドと静的ファイル生成

    yarn build  # または npm run build
    yarn export # または npm run export

    serve で out/ フォルダをホスト

    serve out

    成功すると以下のような画面になります。

  • Git filter-repo 既にコミットしてしまった不要ファイルを履歴から消す方法

    Git filter-repo 既にコミットしてしまった不要ファイルを履歴から消す方法

    Gitでうっかり大容量のファイルや本来コミットしたくなかったファイルを登録してしまった…。

    • ビルド結果やキャッシュが混ざっていた
    • 100MBを超えるファイルをPushしようとしてGitHubに拒否された(今回の私)
    • セキュリティ上問題のあるファイル(APIキーや秘密情報)を誤ってコミット

    こうしたファイルは、単に .gitignore を修正して git rm –cached しても、過去のコミット履歴に残り続けます。

    今回は git filter-repo(旧 git filter-branch の後継ツール)の紹介です。過去コミットも含めて不要ファイルを完全削除し、リポジトリを“きれい”に書き換えてしまうことができます。

    注意点

    履歴を改変すると、既存のブランチと互換性がなくなるため、他の開発者と共有している場合は要注意。

    • もし既にGitHub等にPushしていたら、force push(–force)でリポジトリを上書きしなければなりません。

    • 作業前にバックアップを取っておくことを強く推奨します。

    やり方

    手順1: git filter-repo をインストール

    macOS

    brew install git-filter-repo

    Windows / Linux

    git-filter-repo公式GitHubページ からスクリプトをダウンロードしてパスに配置、あるいはパッケージマネージャ等でインストール。

    インストールできたら git filter-repo –help が実行できるか確認してみましょう。

    手順2: 不要ファイルを削除する

    2-1. 例: 特定のファイルを削除

    git filter-repo --path path/to/your_file.ext --invert-paths

    • –path: 削除(除外)したいファイルパス

    • –invert-paths: 指定したパスを履歴ごと消すオプション

    これで過去のコミットからも your_file.ext が完全に消え、存在しなかったことになります。

    2-2. ディレクトリごと削除

    git filter-repo --path path/to/unwanted_folder --invert-paths

    • unwanted_folder 以下のファイルすべてが対象。

    2-3. 複数のパスを一度に削除

    git filter-repo \
     --path path/to/big_file_1 \
     --path path/to/big_file_2 \
     --invert-paths

    改行やバックスラッシュで続けて書けば、複数ファイル・フォルダをまとめて消せます。

    手順3: ローカル確認 & force push

    filter-repo の実行が終わると、履歴を書き換えた状態がローカルに反映されます。

    変更をリモートに反映するには、通常は強制プッシュが必要です。

    git push origin main --force

    • これでリモート側も「不要ファイルが含まれていない」新しい履歴に上書きされます。

    • コラボレーターがいる場合は衝突や再クローンなどが必要になってくるので、事前に相談してください。

    サイズで一括削除する

    「50MBを超えるファイルをすべて消す」といった雑なやり方をしたい場合は、以下のようなコマンドも便利です。

    git filter-repo --strip-blobs-bigger-than 50M

    これで50MBを超えるファイルは履歴から抹消されます。

    ただし、どのファイルが削除対象になったかを確認してからにするのがおすすめです。

  • Flutter App icon を設定する

    Flutter App icon を設定する

    iOS, Android でアプリリリースするタイミングでアイコンが必要になります。

    iOSの場合は Runner.xcworkspace に自分で設定してしまうと、リリース時にエラーの原因になります。

    今回はflutterの安全なアイコンの設定方法についてです。

    やり方

    最小構成は以下の通りです。

    pubspec.yaml に追加します。(それぞれのプラットフォームごとに対応させるにはこちらを参考ください:https://pub.dev/packages/flutter_launcher_icons)

    dev_dependencies:
      flutter_launcher_icons: "^0.14.3"
    
    flutter_launcher_icons:
      android: "launcher_icon"
      ios: true
      image_path: "assets/icon/icon.png"

    できたらパッケージを実行します。

    flutter pub get
    dart run flutter_launcher_icons

    実行して、VSCodeのiosディレクトリの上で右クリック、Xcode で開きます。

    そうすると、アイコンがセットされているのを確認できます。

    参考

    https://pub.dev/packages/flutter_launcher_icons

  • ストア公開 D-U-N-S ナンバーを取得しよう

    ストア公開 D-U-N-S ナンバーを取得しよう

    🐏 コラムです。

    法人の場合、Google Play Store や Apple Store にアプリリリースするにあたり、DUNS No.が必要になる場合があります。

    この記事ではその手順について解説します。

    DUNSとは?

    DUNS番号(D-U-N-S Number)は、企業や団体の信用情報を管理するためにDun & Bradstreet(D&B)が発行する識別番号です。

    https://www.dnb.com/en-us/smb/duns/get-a-duns.html#gad

    手順

    Appleのデベロッパーの場合、以下のページから無料で検索、申請ができます。
    ぶっちゃけ一番UIが良くて使いやすいのでAppleから取得すると良いなと個人的に感じました。
    https://developer.apple.com/support/D-U-N-S

    手順に従って住所や電話番号を入力するだけです!

    できた!

  • Flutter iOSでビルドできない No such module ‘Flutter’ Thread 1: Fatal error: Unexpectedly found nil while unwrapping an Optional value

    Flutter iOSでビルドできない No such module ‘Flutter’ Thread 1: Fatal error: Unexpectedly found nil while unwrapping an Optional value

    Flutter で iOS用にbuild するも下記のエラーが AppDelegate.swift で起こってしまい、実行できません。

    No such module ‘Flutter’

    Thread 1: Fatal error: Unexpectedly found nil while unwrapping an Optional value

    この記事はこれの解決方法です。

    解決方法

    Flutter プロジェクトのキャッシュを削除します。依存関係の問題を解消するために行います。

    flutter clean

    pubspec.yaml に従ってパッケージのダウンロードや更新、依存関係のセットアップを行います。

    flutter pud get

    ios ディレクトリに移動ます。

    cd ios

    ios/Podfile に基づいて Pods/ フォルダを作成します。また、ios/Podfile.lockを更新し、依存関係のバージョンを固定します。

    pod install

    xcode を書きコマンドで開きます。 Runner.xcodeproj ではないので注意⚠️

    open Runner.xcworkspace

    これで解決しない場合

    flutter をアップグレードしてみましょう。(私はこれで解決しました……。トホホ)アップグレードしたらまた、clean, pub get, pod install の手順を踏んでください。

    flutter upgrade

  • XCode 環境変数追加 Edit Scheme…

    XCode 環境変数追加 Edit Scheme…

    Flutter のプロジェクトで

    import Flutter
    import UIKit
    import GoogleMaps
    
    @main
    @objc class AppDelegate: FlutterAppDelegate {
      override func application(
        _ application: UIApplication,
        didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
      ) -> Bool {
        GMSServices.provideAPIKey(ProcessInfo.processInfo.environment["MY_API_KEY"]!)
        GeneratedPluginRegistrant.register(with: self)
        return super.application(application, didFinishLaunchingWithOptions: launchOptions)
      }
    }
    Thread 1: Fatal error: Unexpectedly found nil while unwrapping an Optional value

    のエラーが出ました。

    この際直打ちのAPIキーはやめて、環境変数に設置することにしました。

    やり方

    XCodeでプロジェクト名のところをクリックすると Edit Scheme… という項目があるのでクリックします。

    Environment Variables のところに + ボタンを押して、追加します。

    GOOGLE_MAPS_API_KEY
    
    hogehogehogehogehogehogehogeho

    みたいな感じで Name, Value に設定しました。

    その後、AppDelegate .swiftを以下のように書き換えました。

    import Flutter
    import UIKit
    import GoogleMaps
    
    @main
    @objc class AppDelegate: FlutterAppDelegate {
      override func application(
        _ application: UIApplication,
        didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
      ) -> Bool {
        
          // Xcode の環境変数から APIキー を取得
          if let apiKey = ProcessInfo.processInfo.environment["GOOGLE_MAPS_API_KEY"], !apiKey.isEmpty {
              GMSServices.provideAPIKey(apiKey)
          } else {
              fatalError("Google Maps API Key is missing. Please set GOOGLE_MAPS_API_KEY in Xcode's Environment Variables.")
          }
    
        GeneratedPluginRegistrant.register(with: self)
        return super.application(application, didFinishLaunchingWithOptions: launchOptions)
      }
    }
    

    これで動作しました!やったー

  • Ubuntu 毎日決まった時間に再起動をかける crontab

    Ubuntu 毎日決まった時間に再起動をかける crontab

    Ubuntu を自宅サーバーとして運用する際に、一定期間に一度再起動したいものです。

    システムやサービスが長期間動作することで、まれにハングアップや不具合が発生することがあります。定期的に再起動することで、こうした問題を未然に防ぎ、安定した運用が可能になります。

    環境

    • Ubuntu 24.04.1 LTS
    • version 24.04

    2025/02/06時点

    やり方

    crontab コマンドを使います。このコマンドは定期的にコマンドやスクリプトを自動実行するた目につかいます。

    cron ジョブの編集画面を開きます。

    sudo crontab -e

    エディタを選択して!と言われるので、nano を選択します。

    crontab

    スケジュールを追加します。今回は毎日午前3時に再起動する設定にしました。

    0 3 * * * /sbin/shutdown -r now

    書き方

    • 0 3 * * *:毎日3時0分に実行
    • 0:分(0分)
    • 3:時(3時)
    • *:日(毎日)
    • *:月(毎月)
    • *:曜日(毎週)
    crontab edit

    入力したら、ctrl O で保存して、return, ctrl X で抜けましょう。

    確認

    以下コマンドで確認できます。

    sudo systemctl status cron

    ログは以下に記録されます。

    grep CRON /var/log/syslog

  • Flutter Webでの Firebase API Key の読み込み クロスプラットフォーム対応

    Flutter Webでの Firebase API Key の読み込み クロスプラットフォーム対応

    Flutter でWebで動作させる場合にAPI Keyを直書きしない方法について解説します。

    通常 index.html にそのまま書けば動作します。

    <body>
      <script src="flutter_bootstrap.js" async></script>
      <script>
        const apiKey = "{{API_KEY}}"; // Dartコードで置換
        const script = document.createElement('script');
        script.src = `https://maps.googleapis.com/maps/api/js?key=ここにAPI KEYベタ打ち`;
        document.head.appendChild(script);
      </script>
    </body>

    しかしこれだと、Gitなどでの管理ができなくなってしまうため、.env ファイルから読み出したいところです。しかしindex.html から直接.env を参照することはできません。

    解決方法

    プラットフォームごとの処理を追加

    Web の場合は以下のファイルを読み込むようにします。 lib の直下に utils ディレクトリを作成し、web_utils.dart を作成しました。

    このようにファイルを分けることで、
    import ‘dart:html’ as html;
    が使えるようになります。

    このパッケージはWebのみで動作するため、他のプラットフォームのコードに含められないからです。

    // ignore: avoid_web_libraries_in_flutter
    import 'dart:html' as html;
    
    void addGoogleMapsScript(String apiKey) {
      final script = html.ScriptElement()
        ..src = 'https://maps.googleapis.com/maps/api/js?key=$apiKey&callback=initMap'
        ..type = 'text/javascript'
        ..async = true
        ..defer = true;
      html.document.body!.append(script);
    }

    Web 以外(iOS, Android)の場合の処理を書いたファイルも用意します。utils ディレクトリに stub_utils.dart を作成。以下のように書きました。

    void addGoogleMapsScript(String apiKey) {
      // モバイルプラットフォームでは何もしない
    }

    main.dart での処理

    flutter_dotenv パッケージを追加します。

    flutter pub add flutter_dotenv
    import 'package:flutter/material.dart';
    import 'package:firebase_core/firebase_core.dart';
    import 'package:flutter_dotenv/flutter_dotenv.dart';
    import 'firebase_options.dart';
    import 'widgets/bottom_nav_layout.dart';
    import 'package:flutter/foundation.dart' show kIsWeb;
    
    // 条件付きインポート
    import 'utils/web_utils.dart'
      if (dart.library.html) 'utils/web_utils.dart'
      if (dart.library.io) 'utils/stub_utils.dart';
    
    void main() async {
      WidgetsFlutterBinding.ensureInitialized();
      
      // 環境変数を読み込む
      await dotenv.load(fileName: ".env");
    
      // Firebaseの初期化
      await Firebase.initializeApp(
        options: DefaultFirebaseOptions.currentPlatform,
      );
    
      // Webの場合のみGoogle Mapsスクリプトを追加
      if (kIsWeb) {
        final apiKey = dotenv.env['GOOGLE_MAPS_API_KEY'];
        if (apiKey != null && apiKey.isNotEmpty) {
          addGoogleMapsScript(apiKey);
        } else {
          print('Error: GOOGLE_MAPS_API_KEY is not set in the .env file.');
        }
      }
    
      runApp(const MyApp());
    }
    
    class MyApp extends StatelessWidget {
      const MyApp({super.key});
    
      @override
      Widget build(BuildContext context) {
        return MaterialApp(
          title: 'Anonymous Login Demo',
          theme: ThemeData(
            useMaterial3: true,
            colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepOrange),
          ),
          home: const BottomNavLayout(currentIndex: 0),
        );
      }
    }

    firebase console で web を追加

    firebase console でモバイルだけでなく、webを追加して、apikey を発行しましょう。

    これを.envに追加します。

    .evn ファイルの設定

    以下のように設定します。
    api_key_here のところで先ほど設定したapi key を入力しましょう。

    GOOGLE_MAPS_API_KEY=your_google_maps_api_key_here
    
    # Firebase iOS Configuration
    FIREBASE_IOS_API_KEY=your_firebase_ios_api_key_here
    FIREBASE_IOS_APP_ID=your_firebase_ios_app_id_here
    FIREBASE_IOS_MESSAGING_SENDER_ID=your_firebase_ios_messaging_sender_id_here
    FIREBASE_IOS_PROJECT_ID=your_firebase_ios_project_id_here
    FIREBASE_IOS_STORAGE_BUCKET=your_firebase_ios_storage_bucket_here
    FIREBASE_IOS_BUNDLE_ID=your_firebase_ios_bundle_id_here
    
    # Firebase Web Configuration
    FIREBASE_WEB_API_KEY=your_firebase_web_api_key_here
    FIREBASE_WEB_AUTH_DOMAIN=your_firebase_web_auth_domain_here
    FIREBASE_WEB_PROJECT_ID=your_firebase_web_project_id_here
    FIREBASE_WEB_STORAGE_BUCKET=your_firebase_web_storage_bucket_here
    FIREBASE_WEB_MESSAGING_SENDER_ID=your_firebase_web_messaging_sender_id_here
    FIREBASE_WEB_APP_ID=your_firebase_web_app_id_here
    FIREBASE_WEB_MEASUREMENT_ID=your_firebase_web_measurement_id_here

    firebase の設定 firebase_options.dart

    プラットフォームごとに api key を切り分けるのに使います。

    import 'package:firebase_core/firebase_core.dart' show FirebaseOptions;
    import 'package:flutter/foundation.dart' show defaultTargetPlatform, kIsWeb, TargetPlatform;
    import 'package:flutter_dotenv/flutter_dotenv.dart';
    
    /// Default [FirebaseOptions] for use with your Firebase apps.
    class DefaultFirebaseOptions {
      static FirebaseOptions get currentPlatform {
        if (kIsWeb) {
          return web;
        }
        switch (defaultTargetPlatform) {
          case TargetPlatform.android:
            throw UnsupportedError('DefaultFirebaseOptions have not been configured for android.');
          case TargetPlatform.iOS:
            return ios;
          case TargetPlatform.macOS:
            throw UnsupportedError('DefaultFirebaseOptions have not been configured for macos.');
          case TargetPlatform.windows:
            throw UnsupportedError('DefaultFirebaseOptions have not been configured for windows.');
          case TargetPlatform.linux:
            throw UnsupportedError('DefaultFirebaseOptions have not been configured for linux.');
          default:
            throw UnsupportedError('DefaultFirebaseOptions are not supported for this platform.');
        }
      }
    
      static final FirebaseOptions ios = FirebaseOptions(
        apiKey: dotenv.env['FIREBASE_IOS_API_KEY']!,
        appId: dotenv.env['FIREBASE_IOS_APP_ID']!,
        messagingSenderId: dotenv.env['FIREBASE_IOS_MESSAGING_SENDER_ID']!,
        projectId: dotenv.env['FIREBASE_IOS_PROJECT_ID']!,
        storageBucket: dotenv.env['FIREBASE_IOS_STORAGE_BUCKET']!,
        iosBundleId: dotenv.env['FIREBASE_IOS_BUNDLE_ID']!,
      );
    
      static final FirebaseOptions web = FirebaseOptions(
        apiKey: dotenv.env['FIREBASE_WEB_API_KEY']!,
        authDomain: dotenv.env['FIREBASE_WEB_AUTH_DOMAIN']!,
        projectId: dotenv.env['FIREBASE_WEB_PROJECT_ID']!,
        storageBucket: dotenv.env['FIREBASE_WEB_STORAGE_BUCKET']!,
        messagingSenderId: dotenv.env['FIREBASE_WEB_MESSAGING_SENDER_ID']!,
        appId: dotenv.env['FIREBASE_WEB_APP_ID']!,
        measurementId: dotenv.env['FIREBASE_WEB_MEASUREMENT_ID'],
      );
    }

  • Flutter build iOS error ビルドできない

    Flutter build iOS error ビルドできない

    flutter build ios

    としたところ、以下のようなエラーが出てきた。

    ══════════ No valid code signing certificates were found You can connect to your Apple Developer account by signing in with your Apple ID in Xcode and create an iOS Development Certificate as well as a Provisioning Profile for your project by:   1- Open the Flutter project's Xcode target with        open ios/Runner.xcworkspace   2- Select the 'Runner' project in the navigator then the 'Runner'   target      in the project settings   3- Make sure a 'Development Team' is selected under Signing &   Capabilities > Team.      You may need to:          - Log in with your Apple ID in Xcode first          - Ensure you have a valid unique Bundle ID          - Register your device with your Apple Developer Account          - Let Xcode automatically provision a profile for your app   4- Build or run your project again   5- Trust your newly created Development Certificate on your iOS   device      via Settings > General > Device Management > [your new      certificate] > Trust  For more information, please visit:   https://developer.apple.com/library/content/documentation/IDEs/Conce   ptual/   AppDistributionGuide/MaintainingCertificates/MaintainingCertificates   .html  Or run on an iOS simulator without code signing ══════════════════════════════════════════════════════════════════════ ══════════ No development certificates available to code sign app for device deployment
    ══════════
    No valid code signing certificates were found
    You can connect to your Apple Developer account by signing in with
    your Apple ID
    in Xcode and create an iOS Development Certificate as well as a
    Provisioning
    Profile for your project by:
      1- Open the Flutter project's Xcode target with
           open ios/Runner.xcworkspace
      2- Select the 'Runner' project in the navigator then the 'Runner'
      target
         in the project settings
      3- Make sure a 'Development Team' is selected under Signing &
      Capabilities > Team.
         You may need to:
             - Log in with your Apple ID in Xcode first
             - Ensure you have a valid unique Bundle ID
             - Register your device with your Apple Developer Account
             - Let Xcode automatically provision a profile for your app
      4- Build or run your project again
      5- Trust your newly created Development Certificate on your iOS
      device
         via Settings > General > Device Management > [your new
         certificate] > Trust
    
    For more information, please visit:
      https://developer.apple.com/library/content/documentation/IDEs/Conce
      ptual/
      AppDistributionGuide/MaintainingCertificates/MaintainingCertificates
      .html
    
    Or run on an iOS simulator without code signing
    ══════════════════════════════════════════════════════════════════════
    ══════════
    No development certificates available to code sign app for device
    deployment
    

    解決法

    terminal で ios/Runner.xcworkspace ファイルを開く

    open ios/Runner.xcworkspace
    XCode
    こんな感じで xcode が立ち上がる。

    Xcodeで「Runner」プロジェクトを選択

    「Signing & Capabilities」タブを開く

    私の場合、久しぶりのストアアップロードで規約に同意していないのが原因でした。

    Unable to process request – PLA Update available
    You currently don’t have access to this membership resource. To resolve this issue, agree to the latest Program License Agreement in your developer account.

    StoreConnectから同意したらできました。

    “Automatically manage signing”(自動管理) をチェック

    「Team」のプルダウンから Apple Developer アカウントを選択

    「Provisioning Profile」が作成されるのを確認

    以上です。

    再度以下のコマンドでビルドしなおしましょう。

    flutter build iOS

  • 【Flutter】WebでいうところのDivタグ

    【Flutter】WebでいうところのDivタグ

    FlutterでHTMLの <div> タグのような機能を持つウィジェットはいくつかあります。

    1. Container(基本のDiv)

    最も基本的な <div> の代わりになるのが Container です。

    Container(
    
      width: 200,
    
      height: 100,
    
      color: Colors.blue,
    
      child: Center(
    
        child: Text("Container", style: TextStyle(color: Colors.white)),
    
      ),
    
    )

    • CSSの div に最も近い要素
    • 幅・高さ・背景色などを指定できる

    2. SizedBox(幅・高さだけ指定)

    CSSの <div style=”width: 100px; height: 100px;”> のように、幅と高さだけ指定するなら SizedBox が便利です。

    SizedBox(
    
      width: 100,
    
      height: 100,
    
      child: Center(child: Text("SizedBox")),
    
    )
    • 背景色なしのシンプルなブロックを作れる

    3. Column / Row(複数の要素を並べる)

    <div> で要素を縦・横に並べるなら Column や Row を使います。

    Column(
    
      children: [
    
        Text("上のテキスト"),
    
        Container(
    
          width: 100,
    
          height: 50,
    
          color: Colors.blue,
    
          child: Center(child: Text("青いボックス")),
    
        ),
    
      ],
    
    )
    • 縦方向に要素を並べる(横方向なら Row)

    4. Stack(重ねる)

    <div> の position: absolute; に相当するのが Stack です。

    Stack(
    
      children: [
    
        Container(width: 200, height: 100, color: Colors.blue),
    
        Positioned(
    
          left: 50,
    
          top: 20,
    
          child: Text("重ねたテキスト", style: TextStyle(color: Colors.white)),
    
        ),
    
      ],
    
    )
    • 要素を自由に配置できる

    5. Expanded(親の幅いっぱいにする)

    CSSの width: 100% に相当するのが Expanded です。

    Row(
    
      children: [
    
        Expanded(child: Container(height: 50, color: Colors.red)),
    
        Expanded(child: Container(height: 50, color: Colors.blue)),
    
      ],
    
    )
    • 親の幅いっぱいに広げる(flex-grow: 1 のような動き)

    6. Flexible(割合で幅を調整)

    CSSの flex: 2 1 auto; のような動きをするのが Flexible です。

    Row(
    
      children: [
    
        Flexible(flex: 2, child: Container(height: 50, color: Colors.red)),
    
        Flexible(flex: 1, child: Container(height: 50, color: Colors.blue)),
    
      ],
    
    )
    • 要素の幅を割合で調整できる

    結論

    目的 Flutterのウィジェット

    <div> の基本機能Container
    サイズのみ指定SizedBox
    要素を縦に並べるColumn
    要素を横に並べるRow
    要素を重ねるStack
    幅を親に合わせる (width: 100%)Expanded
    幅の割合を調整 (flex)Flexible

    Flutterでは Container が最も <div> に近いが、レイアウトに応じて Column / Row / Stack も活用するのがポイント

  • コラム 文字の無いボタンには aria-label をつけよう

    コラム 文字の無いボタンには aria-label をつけよう

    🐏 この記事はコラムです

    ユーザー補助の観点から、文字の無いボタンは、スクリーンリーダーを使用しているユーザーにとっては「ボタン」として読み上げられてしまい、どのような意味なのかわかりません。

    Google PageSpeed Insights では上記画像のような警告が出てしまいます。

    改善方法として button タグに以下のように aria-label を仕込みましょう。

    <button aria-label="ホームーページへ移動">
      <Button
        variant="plain"
        onClick={() => handleNavigate('/')}
        aria-label="ホームページに移動"
      >

  • JOY UI で全体のスタイルを変更する方法 TS 対応

    JOY UI で全体のスタイルを変更する方法 TS 対応

    この記事は?

    Joy UI を使ってUIを構築する際、フォントやPrimary Color などの共通のスタイルを変更する方法についてです。
    個別に変更するばは sx プロップスを使用すればいいのですが、全体の変更する場合は extendTheme を使います。
    以下解説です。

    よしなに。

    環境

    • React TS
    • Joy UI v5.0.0-beta.49
    • 2025.01.20 時点

    やり方

    以下のように thmes ディレクトリなどを作成し、その下にtheme.ts ファイルを作成します。

    中身は例えば以下のようにしてみましょう。
    extendTheme を使うのがポイントです。

    // src/themes/theme.ts
    import { extendTheme } from '@mui/joy/styles';
    
    const theme = extendTheme({
      colorSchemes: {
        light: {
          palette: {
            primary: {
              50: '#e3f2fd',
              100: '#bbdefb',
              200: '#90caf9',
              300: '#64b5f6',
              400: '#42a5f5',
              500: '#2196f3',
              600: '#1e88e5',
              700: '#1976d2',
              800: '#1565c0',
              900: '#0d47a1',
            },
          },
        },
      },
      fontFamily: {
        display: 'Roboto, sans-serif',
        body: 'Arial, sans-serif',
      },
    });
    
    export default theme;

    続いてこの関数を App.tsx で読み込みます。

    CssVarsProvider で全てを囲ってあげます。そして、themeに先ほど作成したtheme.tsを渡してあげればスタイルが下層の全てのコンポーネントに適応されます。

    import React from 'react';
    import { BrowserRouter as Router, Route, Routes } from 'react-router-dom';
    import './styles/App.css';
    import { RecoilRoot } from 'recoil';
    
    // theme
    import { CssVarsProvider } from '@mui/joy';
    import theme from './themes/theme';
    
    // component
    // 省略
    
    function App() {
      return (
        <RecoilRoot>
          <CssVarsProvider theme={theme}>
          <Router>
            <Header />
            <Analytics />
            <Routes>
              <Route path="/" element={<Top />} />
            </Routes>
          </Router>
          </CssVarsProvider>
        </RecoilRoot>
      );
    }
    
    export default App;

    たったこれだけで、プロジェクト全体に変更をかけられるようになりました!
    お疲れ様です!!

    参考

    ↓ 公式ドキュメント様

    https://mui.com/joy-ui/customization/approaches

    おまけ

    mac, windows, 他OSでフォントを切り替えられるようにしてみました。

    // 
    //  このファイルは、JoyUI のテーマを定義するファイルです。
    //  created date: 2025.01.20
    //  created by: coiai
    // 
    
    import { extendTheme } from '@mui/joy/styles';
    
    // OSごとのフォント設定
    const getSerifFont = () => {
      const platform = navigator.platform.toLowerCase();
    
      if (platform.includes('win')) {
        return 'Yu Mincho, serif'; // Windows の場合は遊明朝
      } else if (platform.includes('mac')) {
        return 'Hiragino Mincho Pro, serif'; // Mac の場合はヒラギノ明朝
      } else {
        return 'serif'; // その他のOSはデフォルトのセリフ体
      }
    };
    
    const theme = extendTheme({
      colorSchemes: {
        light: {
          palette: {
            primary: {
              50: '#e3f2fd',
              100: '#bbdefb',
              200: '#90caf9',
              300: '#64b5f6',
              400: '#42a5f5',
              500: '#2196f3',
              600: '#1e88e5',
              700: '#1976d2',
              800: '#1565c0',
              900: '#0d47a1',
            },
          },
        },
      },
      fontFamily: {
        display: getSerifFont(), // 見出し用フォント
        body: getSerifFont(),    // 本文用フォント
      },
    });
    
    export default theme;
    明朝体に変更した画面
  • React で構築した SPA のSEO対策一覧

    React で構築した SPA のSEO対策一覧

    今回WordPress Rest API とReactを用いてサイトを構築しました。
    その際に行ったSEO対策についてメモしておきます。

    URLに直接アクセスした時の対応

    React は基本的にSPAなので、サーバーが全てのリクエストをReact アプリのエントリーポイントにリダイレクトするように設定する必要があります。

    .htaccess に以下の記述をします。

    <IfModule mod_rewrite.c>
      RewriteEngine On
      RewriteCond %{REQUEST_FILENAME} !-f
      RewriteCond %{REQUEST_FILENAME} !-d
      RewriteRule ^ /index.html [L]
    </IfModule>

    GA4 の設定

    パッケージを用いる場合は react-ga4 を使ってみましょう。

    私は Analytics.ts というファイルを作り、App.tsx に読み込ませました。

    import { useEffect } from 'react';
    import { useLocation } from 'react-router-dom';
    import ReactGA from 'react-ga4';
    
    const TRACKING_ID = 'G-yourcode'; // GoogleアナリティクスのトラッキングIDを設定
    
    ReactGA.initialize(TRACKING_ID);
    
    function Analytics() {
      const location = useLocation();
    
      useEffect(() => {
        // ページが変更されるたびにトラッキング情報を送信
        ReactGA.send({ hitType: 'pageview', page: location.pathname });
      }, [location]);
    
      return null; // UIに表示するものはない
    }
    
    export default Analytics;
    function App() {
      return (
        <RecoilRoot>
          <Router>
            <Header />
            <Analytics />
            <Routes>
              <Route path="/" element={<top />} />
            </Routes>
          </Router>
        </RecoilRoot>
      );
    }

    メタタグとOGタグの設定

    React-helmet だと現在の環境ではエラーの原因になります。react-helmet-asyncを使いましょう。

    React で動的にメタ情報を設定するには react-helmet-asyncや@tankstack/react-head を使います。

    以下はReact helmet-async を使った例です。

    import React from 'react';
    import ReactDOM from 'react-dom/client'
    import './styles/index.css';
    import App from './App';
    import reportWebVitals from './reportWebVitals';
    
    // helmet
    import { HelmetProvider } from 'react-helmet-async';
    
    const root = ReactDOM.createRoot(
      document.getElementById('root') as HTMLElement
    );
    root.render(
      <React.StrictMode>
        <HelmetProvider>
          <App />
        </HelmetProvider>
      </React.StrictMode>
    );
    
    // If you want to start measuring performance in your app, pass a function
    // to log results (for example: reportWebVitals(console.log))
    // or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals
    reportWebVitals();
    
    import { Helmet } from "react-helmet-async";
    
    function MyPage() {
      return (
        <div>
          <Helmet>
            <title>ページタイトル</title>
            <meta name="description" content="ページの説明文" />
            <meta property="og:title" content="ページタイトル" />
            <meta property="og:description" content="ページの説明文" />
            <meta property="og:image" content="画像URL" />
            <meta name="robots" content="index, follow" />
          </Helmet>
          <h1>コンテンツ</h1>
        </div>
      );
    }

    https://www.npmjs.com/package/react-helmet-async ←参考ドキュメント

    Sitemap, robots.txt の設定

    react-router-sitemap を使用して自動生成(node.js を使っている場合)
    または手動で追加します。

    手順を別記事にしました。

    構造化データのマークアップ

    JSON-LDを使う。

    <script type="application/ld+json">
    {
      "@context": "https://schema.org",
      "@type": "WebPage",
      "name": "ページタイトル",
      "description": "ページの説明文",
      "url": "https://example.com"
    }
    </script>

    SSR サーバーサイドレンダリングの導入

    Next.js などのフレームワークを使用して、SSRを実装する。

    動的レンダリング

    Rendertron, Prerender.io を使う。

    ↓ Google 検索セントラル公式のRedertron についての解説https://developers.google.com/search/blog/2019/01/dynamic-rendering-with-rendertron?hl=ja

    Rendertron の軽い理解

    Rendertron は、検索エンジンが JavaScript アプリケーションを正しくインデックスできるよう、静的 HTML を生成して提供するツールです。

    1. ユーザーリクエスト

    通常のユーザーリクエストは JavaScript アプリ(React アプリ)をそのまま提供します。

    2. クローラーリクエスト

    Googlebot や他のクローラーからのリクエストを検知し、Rendertron を使用して静的 HTML を生成して提供します。

    SSG 静的サイト生成

    コンテンツが更新されない場合は各ページのHTMLを事前に生成することができる。

    Gatsby などの生成ツールを使うのが一つの手です。

    もしくはreact 単体で動作させる場合は react-snap を使うという手があります。

  • React 単体のプロジェクトに sitemap を導入してSEOスコアを向上させる

    React 単体のプロジェクトに sitemap を導入してSEOスコアを向上させる

    React単体でsitemap を導入する方法について解説します。

    通常はバックエンドに他のフレームワークを使っている場合は、そちらでやるのが一般的だと思いますが、React をビルドしてシンプルに運用している場合は、自分で作るしかありません。

    やり方 手動

    public ディレクトリの下に sitemap.xml を作成します。

    そこにたとえば以下のように記述します。

    <?xml version="1.0" encoding="UTF-8"?>
    <urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
      <url>
        <loc>https://yourdomain.com/</loc>
        <priority>1.0</priority>
      </url>
      <url>
        <loc>https://yourdomain.com/company</loc>
        <priority>0.8</priority>
      </url>
      <url>
        <loc>https://yourdomain.com/office-treatment</loc>
        <priority>0.8</priority>
      </url>
      <!-- 他のルートをここに追加 -->
    </urlset>

    続いて robots.txt に以下のように記述します。

    Sitemap: https://your-page.com/sitemap.xml

    your-page のところはあなたのドメインに置き換えましょう。

Home
About
Blog
Works
Contact