( SSR は Firebase Functionsへ。)
npx create-next-app@latest --use-npm <アプリ名>
https://console.firebase.google.com/ からプロジェクトを作成します。プロジェクト名はアプリ名と同じとしておくと良いでしょう。
以下をそれぞれの場所に追記します。
{
"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
vi .firebaserc
{
"projects": {
"default": "<アプリ名>"
}
}
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))
})
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
}
}
npm run serve
・プランを Blaze にアップグレードする
・Firebase Hosting を開始する
npm run deploy
Project ID が 正しくない可能性があります。以下のコマンドからプロジェクトIDを確認します。
firebase projects:list
プロジェクトIDが間違っている場合は、次のファイル内のプロジェクトIDを正しいものに書き変えます。 .firebaserc
なお、SSR は Firebase Functions としてデプロイされるのでSSR可能です。
例: アプリ名 my-test-hosting-app としています
npx create-next-app@latest --use-npm --example with-firebase-hosting my-test-hosting-app
プロジェクト名はアプリ名と同じ my-test-hosting-app とするとわかりやすいです。 プロジェクト名はコピーしてクリップボードに保存しておきます
アプリのルートディレクトリに移動して、そこからコマンドでログインします。(間違いがないように先にログアウトしておきます)
firebase logout
firebase login
.firebaserc を変更する
{
"projects": {
"default": "先ほど保存したプロジェクト名をここにペースト"
}
}
プロジェクト名をコピーし、忘れた時は、次のコマンドで一覧を表示させて、そこからコピーします
firebase projects:list
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
アプリ(ローカル)
http://localhost:5002/
firebase コンソール(ローカル)
http://localhost:4000/
functions の node.js のバージョンを16に設定します
firebase.json
"functions": {
........
"runtime": "nodejs16"
},
npm run deploy
Firebase コンソールの歯車アイコン → プロジェクトの設定 → サービスアカウント → 新しい秘密鍵の生成
で秘密鍵をダウンロードします。
Githubでアプリのリポジトリへ移動 → settings → Secrets and variables → Actions →「New repository secret」
Name : FIREBASE_SERVICE_ACCOUNT_<FirebaseプロジェクトIDを大文字で。>
Secret : ダウンロードした秘密鍵のJSONを貼り付ける
Githubでアプリのリポジトリへ移動 → Settings → Actionsの中のGeneral → General → Workflow permissions を以下の画像のように設定する
GitHub CLI のインストール
brew install gh
gh secret list
vi .github/workflow/firebase-hosting-merge.yml
npm install -g firebase-tools
firebase --version
firebase logout
firebase login
firebase init hosting
npm install firebase
npm i server-only
import "server-only";
を 先頭に記述します。 これをクライアントで描画すると以下のようなエラーがスローされます。
(サーバーサイドで実行された時にエラーがスローされます)
import "client-only";
/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);
import { useRouter } from "next/router";
const { pathname } = useRouter()
import { useRouter } from "next/router";
const { asPath } = useRouter()
location /app/ {
proxy_pass http://localhost:3000/;
}
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;
next.config.js
module.exports = {
assetPrefix: '/hoge'
};
next.config.js
const isProduction = process.env.NODE_ENV === "production";
module.exports = {
assetPrefix: isProduction ? '/app' : '/'
};
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
"scripts": {
......
"cy:open": "cypress open",
"cy:run": "cypress run"
},
あらかじめ実行しておきます
npm run dev
最初に一度起動します
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/')
})
})
npm run cy:open
npm run cy:run
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();
};
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>
useRouter の push または 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
<button onClick={() => router.back()}>
戻る
</button>
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"
}
}
/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
yarn ts-node node_modules/.bin/typeorm migration:show
エンティティ Post の マイグレーションファイルを自動生成する
yarn ts-node node_modules/.bin/typeorm migration:generate -n Post
src/migration/1647220932735-Post.ts といった命名のファイルが自動生成されます
yarn ts-node node_modules/.bin/typeorm migration:run
import MyComp from "../components/MyComp";
↓
import dynamic from "next/dynamic";
let MyComp = dynamic(() => import("../components/MyComp"), {
ssr: false,
});
以上で、SSRが回避されます。
このように記述することもできます
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が回避されます。
ページをリロードしてhtmlソースを見てみます。
<p>About Page</p>
がなければ、SSRされていません。
const DynamicComponentWithNoSSR = dynamic(
() => import('../components/hello3'),
{ ssr: false }
)
React18以上が必要です。必ずバージョンを確認しましょう
const DynamicLazyComponent = dynamic(() => import('../components/hello4'), {
suspense: true,
})
npx create-next-app@latest my-app
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
npx create-next-app -e with-tailwindcss my-project
-e オプションはこちらのリポジトリからデータを持ってきます https://github.com/vercel/next.js/tree/master/examples
公式のリポジトリにサンプルがないためまずTypeScriptでアプリを作成してその後にTailWindを追加します
npx create-next-app@latest --ts my-app
cd my-app
npm install -D tailwindcss@latest postcss@latest autoprefixer@latest
npx tailwindcss init -p
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: [],
}
import 'tailwindcss/tailwind.css';
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>
)
IntelliSense for CSS class names in HTML を無効にしましょう
変更前の _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} />);
}
メインの 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;
変更前の 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>;
};
引用 : 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>
}