フロントエンド開発といえば。
next.js アプリの初期化( npx create-next-app@latest --use-npm <アプリ名> )

すでに作成ずみの Next.js アプリを Firebase Hosting へデプロイする

● すでに作成ずみの Next.js アプリを Firebase Hosting へデプロイする

( SSR は Firebase Functionsへ。)

1. アプリの作成

npx create-next-app@latest --use-npm <アプリ名>

2. firebase コンソールからプロジェクトを作成する

https://console.firebase.google.com/ からプロジェクトを作成します。プロジェクト名はアプリ名と同じとしておくと良いでしょう。

3. package.json を変更する

以下をそれぞれの場所に追記します。

{
  "main": "firebaseFunctions.js",
  "scripts": {
    "serve": "npm run build && firebase emulators:start --only functions,hosting",
    "shell": "npm run build && firebase functions:shell",
    "deploy": "firebase deploy --only functions,hosting",
    "logs": "firebase functions:log"
  },
  "dependencies": {
    "firebase-admin": "^9.4.2",
    "firebase-functions": "^3.13.1",
  },
  "devDependencies": {
    "firebase-functions-test": "^0.2.3",
    "firebase-tools": "^9.3.0"
  }
}

その後にパッケージをインストールします

npm install

4. .firebaserc を追加する

vi .firebaserc
{
  "projects": {
    "default": "<アプリ名>"
  }
}

5. firebaseFunctions.js を追加する

vi firebaseFunctions.js
const { https } = require('firebase-functions')
const { default: next } = require('next')

const nextjsDistDir = './.next/';

const nextjsServer = next({
  dev: false,
  conf: {
    distDir: nextjsDistDir,
  },
})
const nextjsHandle = nextjsServer.getRequestHandler()

exports.nextjsFunc = https.onRequest((req, res) => {
  return nextjsServer.prepare().then(() => nextjsHandle(req, res))
})

5. firebase.json を追加する

vi firebase.json
{
  "hosting": {
    "public": "public",
    "ignore": ["firebase.json", "**/.*", "**/node_modules/**"],
    "rewrites": [
      {
        "source": "**",
        "function": "nextjsFunc"
      }
    ]
  },
  "functions": {
    "source": ".",
    "predeploy": [
      "npm --prefix \"$PROJECT_DIR\" install",
      "npm --prefix \"$PROJECT_DIR\" run build"
    ],
    "runtime": "nodejs16"
  },
  "emulators": {
    "functions": {
      "port": 5001
    },
    "hosting": {
      "port": 5002
    },
    "ui": {
      "enabled": true
    },
    "singleProjectMode": true
  }
}

6. ローカル(エミュレーター)で起動する

npm run serve

http://localhost:5002/

7. firebase console から設定を変更する

・プランを Blaze にアップグレードする
・Firebase Hosting を開始する

8. firebase へ デプロイする

npm run deploy

9. HTTP Error: 403, Project '<プロジェクト名>' not found or permission denied. エラーとなる場合の対処法

Project ID が 正しくない可能性があります。以下のコマンドからプロジェクトIDを確認します。

firebase projects:list

プロジェクトIDが間違っている場合は、次のファイル内のプロジェクトIDを正しいものに書き変えます。 .firebaserc

No.2295
03/13 23:53

edit

テンプレートを使ってNext.js を Firebase Hosting へデプロイする ( SSR は Firebase Functionsへ )

● 1. テンプレートを使って「Next.js を Firebase Hosting へ手動デプロイ」の練習をする

なお、SSR は Firebase Functions としてデプロイされるのでSSR可能です。

1. アプリの作成

例: アプリ名 my-test-hosting-app としています

npx create-next-app@latest --use-npm --example with-firebase-hosting my-test-hosting-app

2. Firebase のコンソールから 新規プロジェクトを作成

プロジェクト名はアプリ名と同じ my-test-hosting-app とするとわかりやすいです。 プロジェクト名はコピーしてクリップボードに保存しておきます

3. firebase cli からログイン

アプリのルートディレクトリに移動して、そこからコマンドでログインします。(間違いがないように先にログアウトしておきます)

firebase logout
firebase login

.firebaserc を変更する

{
  "projects": {
    "default": "先ほど保存したプロジェクト名をここにペースト"
  }
}

プロジェクト名をコピーし、忘れた時は、次のコマンドで一覧を表示させて、そこからコピーします

firebase projects:list

4. ローカルのテスト

firebase.json

ポートを 5002番に変更します

emlators を追加します

{
  "hosting": {
        ........
  } ,
  "functions": {
        ........
  } ,

  "emulators": {
    "functions": {
      "port": 5001
    },
    "hosting": {
      "port": 5002
    },
    "ui": {
      "enabled": true
    },
    "singleProjectMode": true
  }
}

package.json には

"serve": "npm run build && firebase emulators:start --only functions,hosting",

があるので このスクリプトを実行します。

npm run serve

5.ローカルサーバーへアクセスします

アプリ(ローカル)
http://localhost:5002/

firebase コンソール(ローカル)
http://localhost:4000/

6. 本番サーバ ( Firebase Hosting ) へデプロイする

functions の node.js のバージョンを16に設定します

firebase.json

  "functions": {
    ........
    "runtime": "nodejs16"
  },

npm run deploy

● 2. githubへ mainブランチを push したときに自動で Firebase にデプロイする設定を追加する

Firebase のシークレットを githubへ登録する

(シークレットの登録)1. Firebase コンソールからサービスアカウントの作成

Firebase コンソールの歯車アイコン → プロジェクトの設定 → サービスアカウント → 新しい秘密鍵の生成

で秘密鍵をダウンロードします。

(シークレットの登録)2. Githubへサービスアカウントの登録

Githubでアプリのリポジトリへ移動 → settings → Secrets and variables → Actions →「New repository secret」
Name : FIREBASE_SERVICE_ACCOUNT_<FirebaseプロジェクトIDを大文字で。>
Secret : ダウンロードした秘密鍵のJSONを貼り付ける

(シークレットの登録)3. Githubで Workflow の Permission を変更する

Githubでアプリのリポジトリへ移動 → Settings → Actionsの中のGeneral → General → Workflow permissions を以下の画像のように設定する

GitHub CLI のインストール

brew install gh

secretsの確認

gh secret list
vi .github/workflow/firebase-hosting-merge.yml

参考 : GitHub Actionsでfirebase preview channelを作成する - すな.dev

添付ファイル1
No.2294
03/13 23:30

edit

添付ファイル

firebase hosting

● Firebase CLI ( firebase-tools ) のインストール

npm install -g firebase-tools
firebase --version

firebase アカウントが既にある場合は一度ログアウトしてからログインしなおすと良いです

firebase logout
firebase login

firebase init hosting

アプリのディレクトリに移動してFirebaseをインストールする

npm install firebase
No.2292
03/09 18:26

edit

● Next.js のコンポーネントが 「サーバー」/「クライアント」どちらかのみで動作することを限定する

● server-only コンポーネントのインストール

npm i server-only

● サーバーサイドのみに限定する

import "server-only";

を 先頭に記述します。 これをクライアントで描画すると以下のようなエラーがスローされます。

● クライアントサイドのみに限定する

(サーバーサイドで実行された時にエラーがスローされます)

import "client-only";
添付ファイル1
No.2290
03/08 23:08

edit

添付ファイル

Next.js で 多言語

● A. ディレクトリで 言語ごとに分ける方法

/ja/ または ロケールなし の場合は 日本語
/en/ の場合は 英語
とするには以下のように記述します

next.config.js

/** @type {import('next').NextConfig} */
const nextConfig = {
  reactStrictMode: true,
  i18n: {
    locales: ["en", "ja"],
    defaultLocale: "ja",
  },
};

module.exports = nextConfig;

これで

http://localhost:3000/login の場合は日本語
http://localhost:3000/ja/login の場合は日本語
http://localhost:3000/en/login の場合は英語

となります。

● コンポーネントでロケールを取得する

import { usePathname } from 'next/navigation';
const getLocale = (path: string): string => {
  if (path.match(/^\/en/)) return 'en';

  return 'ja';
};


const pathname = usePathname();
const locale = getLocale(pathname);
No.2289
03/07 15:41

edit

next.js で 現在のURLパスを取得する

● next.js で 現在のURLパスを取得する

・/docs/[id]/ で取得する場合

import { useRouter } from "next/router";
const { pathname } = useRouter()

・/docs/123456/ で取得する場合

import { useRouter } from "next/router";
const { asPath } = useRouter()
No.2244
03/15 15:13

edit

next.js の アプリをサブディレクトリで動作させる

● nginxの設定ファイル

      location /app/ {
          proxy_pass http://localhost:3000/;
      }

● next.config.js

assetPrefix を追加します

const SUB_DIRECTORY = "/app";

const isProduction = process.env.NODE_ENV === "production";

/** @type {import('next').NextConfig} */
const nextConfig = {
  assetPrefix: isProduction ? SUB_DIRECTORY : "/" ,
  reactStrictMode: true,
  swcMinify: true,
};

module.exports = nextConfig;
No.2213
03/09 13:12

edit

next.js の ルートURL を変更する

● Next.jsでURLのルートを変更する

next.config.js

module.exports = {
  assetPrefix: '/hoge'
};

● run dev / run build で自動的にセットされる NODE_ENV を使用して設定を分ける場合

next.config.js

const isProduction = process.env.NODE_ENV === "production";
module.exports = {
  assetPrefix: isProduction ? '/app' : '/'
};
No.2212
03/09 13:13

edit

next.js で cypress の E2Eテスト

● nextjsアプリと cypress のインストール

npx create-next-app@latest --ts cypress-testing-app
cd cypress-testing-app

npm install cypress --save-dev
npm install @testing-library/cypress --save-dev

package.json に以下を追加

  "scripts": {
    ......
    "cy:open": "cypress open",
    "cy:run": "cypress run"
  },

● nextjsアプリの実行

あらかじめ実行しておきます

npm run dev

● cypressの起動

最初に一度起動します

npm run cy:run

● テストスクリプトを追加

cypress\e2e\0-my-tests\0-my-sample.cy.js

describe('example to-do app', () => {

  it('ルートパスに訪問できるか', () => {
    cy.visit('http://localhost:3000/')
  })
  
})

● テストを実行(GUI)

npm run cy:open

● テストを実行(CUI)

npm run cy:run
No.2206
03/09 13:14

edit

nextjs middleware

middlewareはサーバサイドです

/src/middleware.ts

import { NextRequest, NextResponse } from 'next/server';

const isClient = () => typeof window !== 'undefined';

export const middleware = (req: NextRequest) => {
  console.log('isClient ?');
  console.log(isClient());
  return NextResponse.next();
};
No.2184
03/08 14:44

edit

Next.js で 画面遷移、1つ前の履歴に戻る

● 1. Link を使用する

import Link from 'next/link'

Next.js version 13以降

<Link href="/about">About Us</Link>

Next.js version 12以前

<Link href="/about"><a>About Us</a></Link>

● 2. onClick など、メソッドで画面遷移したい場合は 「useRouter」または「Router」を使用する

useRouterpush または replace メソッドを使用します

import { useRouter } from 'next/router';

const router = useRouter();

router.push({
  pathname: '/login',
  query: { returnUrl: router.asPath }
})

Router

import Router from 'next/router';

Router.push('/home');  // '/home'へ遷移

Next.jsのRouterとuseRouterは何が違うのか - Qiita

● Next.js で 1つ前の履歴に戻る

      <button onClick={() => router.back()}>
        戻る
      </button>
No.2168
03/08 12:43

edit

Next.js + SQLite

nextjsアプリの初期化

npx create-next-app@latest --ts sample-app
cd sample-app

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

npm install --save typescript ts-node
npm install --save typeorm sqlite3
npm install sqlite3 --save
npm install reflect-metadata --save
npm install @types/node --save
yarn typeorm init --database sqlite3

ormconfig.json が 自動生成されますので、以下のように追記します。

   "type": "sqlite",
   "database": "data/dev.sqlite",    
{
   "type": "sqlite",
   "database": "data/dev.sqlite",    

   "synchronize": true,
   "logging": false,
   "entities": [
      "src/entity/**/*.ts"
   ],
   "migrations": [
      "src/migration/**/*.ts"
   ],
   "subscribers": [
      "src/subscriber/**/*.ts"
   ],
   "cli": {
      "entitiesDir": "src/entity",
      "migrationsDir": "src/migration",
      "subscribersDir": "src/subscriber"
   }
}

テーブル Postを追加してみます

/src/entity/Post.ts

import {
  Entity,
  Column,
  PrimaryGeneratedColumn,
  CreateDateColumn,
  UpdateDateColumn,
} from 'typeorm';

@Entity()
export class Post {
  @PrimaryGeneratedColumn()
  readonly id: number;

  @Column('varchar', { length: 255, nullable: false })
  name: string;

  @Column('int', { nullable: true })
  sort_no: string;

  @CreateDateColumn()
  readonly created_at?: Date;

  @UpdateDateColumn()
  readonly updated_at?: Date;
}

マイグレーションの実行

Windowsのターミナルから

node_modules\.bin\ts-node ./node_modules/typeorm/cli.js  migration:generate -n Post
node_modules\.bin\ts-node ./node_modules/typeorm/cli.js  migration:run

と実行します

https://www.wakuwakubank.com/posts/730-typeorm-custom-naming/

elastomer_appま

●マイグレーション

yarn typeorm init

1. DB接続の確認 と マイグレーションの確認

yarn ts-node node_modules/.bin/typeorm migration:show

2. マイグレーションファイルの自動生成

エンティティ Post の マイグレーションファイルを自動生成する

yarn ts-node node_modules/.bin/typeorm migration:generate -n Post

src/migration/1647220932735-Post.ts といった命名のファイルが自動生成されます

3. マイグレーションの実行

yarn ts-node node_modules/.bin/typeorm migration:run
No.2162
03/09 13:17

edit

Next.js でdynamic import ( ssr: false ) による SSR回避

● Next.js で dynamic import ( ssr: false ) による SSR回避(その1)

import MyComp from "../components/MyComp";

  ↓

import dynamic from "next/dynamic";
let MyComp = dynamic(() => import("../components/MyComp"), {
  ssr: false,
});

以上で、SSRが回避されます。

● Next.js で dynamic import ( ssr: false ) による SSR回避(その2)

このように記述することもできます

import App from '../components/App'

export default function About() {z
  return (
    <App>
      <p>About Page</p>
    </App>
  )
}

  ↓

import dynamic from 'next/dynamic'
import App from '../components/App'

const About = ()=> {
  return (
    <App>
      <p>About Page</p>
    </App>
  )
}

export default dynamic(() => Promise.resolve(About), {
  ssr: false
})

以上で、SSRが回避されます。

● SSR回避の確認方法

ページをリロードしてhtmlソースを見てみます。

<p>About Page</p>

がなければ、SSRされていません。

オプション With no SSR

const DynamicComponentWithNoSSR = dynamic(
  () => import('../components/hello3'),
  { ssr: false }
)

オプション With suspense

React18以上が必要です。必ずバージョンを確認しましょう

const DynamicLazyComponent = dynamic(() => import('../components/hello4'), {
  suspense: true,
})

https://nextjs.org/docs/advanced-features/dynamic-import

No.2158
03/09 21:52

edit

Next.js アプリの初期化

● Next.js アプリの初期化(JavaScript)

npx create-next-app@latest  my-app

● Next.js アプリの初期化(TypeScript)

npx create-next-app@latest --ts my-app

NEXT JS アプリのビルドを高速化させるターボパックを追加するには with-turbopack オプションを追加します

npx create-next-app@latest  my-app  --ts with-turbopack

turbopack でビルドを行うには次のコマンドを実行します

next dev --turbo

● Next.js アプリの初期化(JavaScript + TailWind CSS を追加)

npx create-next-app -e with-tailwindcss my-project

-e オプションはこちらのリポジトリからデータを持ってきます https://github.com/vercel/next.js/tree/master/examples

● Next.js アプリの初期化(TypeScript + TailWind CSS を追加)

公式のリポジトリにサンプルがないためまずTypeScriptでアプリを作成してその後にTailWindを追加します

・1. Next.js アプリの初期化(TypeScript)

npx create-next-app@latest --ts my-app

・2. TailWindのインストールと設定ファイルの初期化

cd my-app
npm install -D tailwindcss@latest postcss@latest autoprefixer@latest
npx tailwindcss init -p

・3. tailwind.config.js に mode と purge を追記する

module.exports = {
  mode: 'jit',
  purge: ['./pages/**/*.{js,ts,jsx,tsx}', './components/**/*.{js,ts,jsx,tsx}'],
  darkMode: false, // or 'media' or 'class'
  theme: {
    extend: {},
  },
  variants: {
    extend: {},
  },
  plugins: [],
}

・4. tailwindを _app.ts から読み込む

import 'tailwindcss/tailwind.css';

・5. index.tsx の JSXを試しに以下のようにする

  return (
    <div className="text-red-500 text-4xl sm:text-6xl lg:text-7xl leading-none font-extrabold tracking-tight mt-10 mb-8 sm:mt-14 sm:mb-10">テストです</div>
  )

● VS Code の IntelliSense for CSS class names in HTML が重い場合

IntelliSense for CSS class names in HTML を無効にしましょう
No.2070
03/09 13:19

edit

Next. js で getLayout パターンで 共通のレイアウトページを作成する

● Next. js でgetLayout パターンで共通のレイアウトページを作成する

1. _app.tsx を変更する

変更前の _app.tsx

import '@/styles/globals.css'
import type { AppProps } from 'next/app'

export default function App({ Component, pageProps }: AppProps) {
  return <Component {...pageProps} />
}

 ↓ 変更後の _app.tsx

import "@/styles/globals.css";
import { ReactElement, ReactNode } from "react";
import { NextPage } from "next";
import type { AppProps } from "next/app";

type NextPageWithLayout = NextPage & {
  getLayout?: (page: ReactElement) => ReactNode;
};

type AppPropsWithLayout = AppProps & {
  Component: NextPageWithLayout;
};

export default function App({ Component, pageProps }: AppPropsWithLayout) {
  const getLayout =
    Component.getLayout ||
    ((page) => {
      return page;
    });

  return getLayout(<Component {...pageProps} />);
}

2 . 共通レイアウト Layout.js の作成

メインの children のところが各ページ内容に置き換わります

mkdir components
vi components/Layout.js

components/Layout.jsx

import React from "react";

interface Props {
  children?: React.ReactNode;
}

const SimpleLayout: React.FC<Props> = ({ children }: Props) => {
  return (
    <>
      <h1>header</h1>
      {/* ===== メイン ===== */}
      <main>{children}</main>
      {/* ===== /メイン ===== */}
      <h1>footer</h1>
    </>
  );
};

export default SimpleLayout;

3 . レイアウトを各ページへ適用

変更前の src/pages/helloworld.tsx

const Helloworld = () => {
  return (
    <div>
      <h1>Helloworld</h1>
    </div>
  );
};
export default Helloworld;

  ↓  変更後の src/pages/helloworld.tsx

import SimpleLayout from "@/layouts/SimpleLayout";
import { ReactNode } from "react";

export default function Helloworld() {
  return (
    <div>
      <h1>ハローワールド</h1>
    </div>
  );
}

Helloworld.getLayout = (page: ReactNode) => {
  return <SimpleLayout>{page}</SimpleLayout>;
};
No.2119
03/07 18:05

edit

Next.js のコンポーネントが 「サーバー」/「クライアント」どちらで動いているかを調べる

● Next.js のコンポーネントが 「サーバー」/「クライアント」どちらで動いているかを調べる

引用 : https://maku.blog/p/m7is4dn/

windowオブジェクトはクライアント(WEBブラウザ)で動作している時のみ存在するのでこれを調べると言う方法です

const isClient = () => typeof window !== 'undefined'
// const isServer = () => typeof window === 'undefined'
const HelloPage: FC = () => {
  if (isClient()) {
    console.log('これはクライアントサイド JS として実行されているよ!')
    console.log(window.location)
    alert('だから alert も使えるよ!')
  }
  return <h1>Hello</h1>
}
No.2066
03/07 15:05

edit