swcMinify:true があると効かないようです。
next.config.js
/** @type {import('next').NextConfig} */
const nextConfig = {
reactStrictMode: true,
// swcMinify: false, // ● この行は必ず削除しましょう
}
module.exports = nextConfig
content の リストの中で使用されているクラスがビルド時に使用できるようになるので、もし反映されないコンポーネントが リストにない場合はそこに追加しましょう。
tailwind.config.ts
import type { Config } from 'tailwindcss'
const config: Config = {
content: [
'./src/features/**/*.{js,ts,jsx,tsx,mdx}', // ● /features/ 以下を追加する場合はこのように追加します
'./src/pages/**/*.{js,ts,jsx,tsx,mdx}',
'./src/components/**/*.{js,ts,jsx,tsx,mdx}',
'./src/app/**/*.{js,ts,jsx,tsx,mdx}'
],
theme: {
extend: {
backgroundImage: {
'gradient-radial': 'radial-gradient(var(--tw-gradient-stops))',
'gradient-conic': 'conic-gradient(from 180deg at 50% 50%, var(--tw-gradient-stops))'
}
}
},
plugins: []
}
export default config
import { useEffect, useState } from 'react'
import Image from 'next/image'
export const FallbackImage = ({ src, ...rest }) => {
const [imgSrc, setImgSrc] = useState(src)
useEffect(() => {
setImgSrc(src)
}, [src])
return (
<Image
{...rest}
src={imgSrc ? imgSrc : '/images/not-found.png'}
onError={() => {
setImgSrc('/images/not-found.png')
}}
/>
)
}
sample.module.css
.btnRed {
background-color: red;
color: white;
}
コンポーネントでの使用
import styles from "@/styles/common.module.css";
return (
<button className={styles.btnRed}>ボタン</button>
)
npm i sass -D
あとはファイル名を sample.module.scss のように拡張子 .scss にするだけです。
コンポーネントでの使用
import styles from "@/styles/common.module.css";
return (
// 存在しないクラス .hogehoge でエラーが出ない
<button className={styles.hogehoge}>ボタン</button>
)
typed-scss-modulesのインストール
npm i typed-scss-modules -D
vi typed-scss-modules.config.ts
以下の内容で保存します
export const config = {
exportType: 'default',
nameFormat: 'none',
implementation: 'sass'
}
型生成コマンド
npx typed-scss-modules src
happy-css-modules のインストール
npm i -D happy-css-modules
型生成コマンド
npx hcm 'src/**/*.module.{css,scss,less}'
src/types/env.d.ts ( ファイル名やディレクトリはどこでもokです )
declare namespace NodeJS {
interface ProcessEnv {
readonly APP_NAME: string;
readonly NEXT_PUBLIC_APP_NAME: string;
}
}
.env
# APP
APP_NAME=アプリ名
NEXT_PUBLIC_APP_NAME=${APP_NAME}
npm install --save-dev npm-run-all opener
package.json
"scripts": {
"dev": "npm-run-all --parallel dev:next dev:browser",
"dev:next": "next dev -p 3999",
"dev:browser": "sleep 1 && opener http://localhost:3999/",
},
実行
npm run dev
npm-run-allのオプション
順次実行
「npm-run-all --serial」または「npm-run-all -s」または「run-s」と記述します
並列実行
「npm-run-all --parallel」または「npm-run-all -p」または「run-p」と記述します
npx create-next-app@latest --use-npm next-emotion-minimal-app
npm install @emotion/react
{
"compilerOptions": {
// emotion
"jsx": "preserve",
"jsxImportSource": "@emotion/react",
src/pages/index.tsx
import { css } from '@emotion/react';
export default function Home() {
return (
<>
<div
css={css`
color: red;
font-size: 48px;
font-weight: bold;
`}
>
home
</div>
</>
);
}
import { css } from '@emotion/react';
const fontLarge = css`
font-size: 48px;
`;
const colorGreen = css`
color: green;
`;
export default function Home() {
return (
<>
<div css={[fontLarge, colorGreen]}>home</div>
</>
);
}
import { css } from '@emotion/react';
const fontLarge = css`
font-size: 48px;
`;
const myFont = css`
${fontLarge}
color: blue;
`;
export default function Home() {
return (
<>
<div css={myFont}>home</div>
</>
);
}
import type { AppProps } from 'next/app';
import { css, Global } from '@emotion/react';
const globalStyle = css`
body {
font-family: 'Helvetica Neue', Arial, 'Hiragino Kaku Gothic ProN',
'Hiragino Sans', Meiryo, sans-serif;
}
`;
export default function App({ Component, pageProps }: AppProps) {
return (
<>
<Global styles={globalStyle}></Global>
<Component {...pageProps} />
</>
);
}
src/pages/index.tsx
import { useState } from 'react';
import { css } from '@emotion/react';
type MyFont = {
colored:boolean
}
export default function Home() {
const [colored, setColored] = useState<boolean>(false);
const myFont = ({ colored }:MyFont) => css`
border: solid 1px black;
border-radius: 10px;
padding: 16px;
cursor: pointer;
${colored &&
`
background-color: #413F42;
color: white;
`}
`;
return (
<>
<button
onClick={() => {
setColored(!colored);
}}
>
色変更
</button>
<div css={myFont({ colored: colored })}>home</div>
</>
);
}
npm install @emotion/styled
こちらで css から js object に 変換します
https://transform.tools/css-to-js
src/pages/index.tsx
import styled from '@emotion/styled';
const MyTitle = styled.h1`
font-size: 3.5rem;
margin: 0px;
color: blue;
`;
export default function Home() {
return (
<>
<MyTitle as={'button'}>home</MyTitle>
</>
);
}
as={'button'} で 出力時にタグを h1 から button に変更しています
npm i zustand
src/stores/book.ts
import { create } from "zustand";
type BookStoreData = {
amount: number;
title: string;
};
const initialData: BookStoreData = {
amount: 0,
title: "",
};
interface BookStore {
book: BookStoreData;
updateAmount: (newAmount: number) => void;
updateTitle: (newTitle: string) => void;
fetchTitle: () => void;
}
export const useBookStore = create<BookStore>((set, get) => ({
book: { ...initialData },
updateAmount: (newAmount: number) => {
const amountState = get().book.amount;
const newBook: BookStoreData = {
amount: newAmount + amountState,
title: get().book.title,
};
set({ book: newBook });
},
updateTitle: (newTitle: string) => {
const titleState = get().book.title;
const newBook: BookStoreData = {
amount: get().book.amount,
title: newTitle,
};
set({ book: newBook });
},
fetchTitle: async () => {
await new Promise((resolve) => setTimeout(resolve, 2000));
const newBook: BookStoreData = {
amount: get().book.amount,
title: "fetched Title",
};
set({ book: newBook });
},
}));
ストア値の参照 : const book = useBookStore((state) => state.book);
アクションfetchTitle の呼び出し : const fetchTitle = useBookStore((state) => state.fetchTitle);
const BookSample = () => {
const book = useBookStore((state) => state.book);
const updateAmount = useBookStore((state) => state.updateAmount);
const updateTitle = useBookStore((state) => state.updateTitle);
const fetchTitle = useBookStore((state) => state.fetchTitle);
const inputRef = useRef<HTMLInputElement>(null);
useEffect(() => {
fetchTitle();
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [fetchTitle]);
return (
<div>
<h1> BookTitle: {book.title} </h1>
<h1> Books: {book.amount} </h1>
<button onClick={() => updateAmount(10)}> Update Amount </button>
<div>
<input type="text" ref={inputRef} />
<button
onClick={() =>
updateTitle(inputRef.current ? inputRef.current.value : "")
}
>
Update Text
</button>
</div>
</div>
);
};
( 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);
i18nにおけるロケールの表は、主にISO 639言語コードとISO 3166国コードに基づいて決定されます。
ISO 639言語コードは、言語を識別するための2〜3文字のコードです。たとえば、英語のISO 639コードは "en" であり、日本語のISO 639コードは "ja" です。
一方、ISO 3166国コードは、国または地域を識別するための2文字または3文字のコードです。たとえば、アメリカ合衆国のISO 3166コードは "US" であり、日本のISO 3166コードは "JP" です。
これらのコードを組み合わせることで、i18nのロケールコードが形成されます。たとえば、英語を話すアメリカ人のためのロケールは、"en_US"と表記されます。同様に、日本語を話す日本人のためのロケールは、"ja_JP"と表記されます。
ただし、ISO規格以外のロケールコードも存在する場合があります。それらは、一般的な業界規格、アプリケーション固有の規則、あるいは地域の文化的および言語的な違いに基づくものがあります。
import { useRouter } from "next/router";
const { pathname } = useRouter()
import { useRouter } from "next/router";
const { asPath } = useRouter()
import { useRouter } from 'next/router';
const router = useRouter();
const docId = router.query.id; // 123456
http://localhost:3002/test?page=12
/pages/ 内の .tsx ファイルのgetServerSideProps()にてURLパラメーターを取得する方法です。 これだと props で コンポーネントに渡されるので router.isReady を待つ必要がありません。
/src/pages/test.tsx
import { MyComponent } from '@/components/MyComponent';
import { GetServerSidePropsContext, NextPage } from 'next';
interface Props {
page: number;
}
export async function getServerSideProps(context: GetServerSidePropsContext):Promise<{props:Props}> {
const { page: queryPage } = context.query;
const page = queryPage ? Number(queryPage) : 999;
return {
props: {
page,
},
};
}
const Test: NextPage<Props> = (props ) => {
return (
<>
<h1>Hello Test</h1>
<h5>{props.page}</h5>
<MyComponent page={props.page} />
</>
);
};
export default Test;
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 を使用する(こちらがおすすめです)
import { useRouter } from 'next/router';
const router = useRouter();
if (router.isReady) {
router.push({
pathname: '/login',
query: { returnUrl: router.asPath }
})
}
Router
import Router from 'next/router';
Router.push('/home'); // '/home'へ遷移
useRouter は hooks なので、 実際にルーターのインスタンス を取得したときにre-render されるので以下のコードが正しく実行できます。 Routerの場合は まだインスタンスがないので実行できません。
○ OK
import { useRouter } from "next/router";
const router = useRouter();
if (router.isReady) {
router.push("/login");
}
× NG
import Router from "next/router";
if (Router.isReady) {
Router.push("/login");
}
<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";
const MyCompNoSSR = dynamic(() => import("./MyComp"), { ssr: false });
以上で、SSRが回避されます。
import MyComp from "../components/MyComp";
↓
import dynamic from "next/dynamic";
const MyCompNoSSR = dynamic(
() => import("./MyComp").then((modules) => modules.MyComp),
{ ssr: false },
);
以上です。
このように 呼び出されるコンポーネント側に記述することもできます
import App from '../components/App'
export default function About() {
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.tsx
components/Layout.tsx
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/mypage/index.tsx
import { Mypage } from '@/features/mypage/Mypage'
import LoggedInLayout from '@/layouts/LoggedInLayout'
import { ReactNode } from 'react'
import { NextPageWithLayout } from '@/pages/_app'
const PagesMypage: NextPageWithLayout = () => {
return <Mypage />
}
PagesMypage.getLayout = (page: ReactNode) => {
return <LoggedInLayout>{page}</LoggedInLayout>
}
export default PagesMypage
使うのは以下の2ファイルに限定すると良いでしょう。
.env.development : 開発用ファイル
NODE_ENV が development ( = npm run dev )の時に読み込まれる。
.env.production : 本番用ファイル
NODE_ENV が production ( = npm run build && npm run start )の時に読み込まれる。
書き方(サーバーサイド)
( .env.development または .env.production)
HOGE=mySettingValue
呼び出し方(サーバーサイド)
( xxx.js や xxx.ts ファイル )
console.log( process.env.HOGE );
書き方(フロントエンド)
( .env.development または .env.production)
NEXT_PUBLIC_HOGE=mySettingValue
呼び出し方(フロントエンド)
( xxx.js や xxx.ts ファイル )
console.log( process.env. NEXT_PUBLIC_HOGE );
NODE_ENV で判断すると良いでしょう
console.log( process.env.NODE_ENV );
typeof window === "undefined"
windowオブジェクトがないのがサーバーサイド
windowオブジェクトがあるのがフロントエンド
です。
デバッグ用にサーバーがクライアントを返したい場合はメソッドにしておいても良いかもです
export default function ServerOfClient() {
return (typeof window === "undefined") ? 'Server' : 'Client';
}
Booleanの場合はそのまま利用しても良いですし以下のようにしても良いです
const isProduction = process.env.NODE_ENV === "production";
console.error(`● process.env.APOLLO_FETCH_POLICY は (${process.env.APOLLO_FETCH_POLICY}) / NODE_ENVは(${process.env.NODE_ENV}) / Server or Client は(${ServerOfClient()})`);
結果例
● process.env.APOLLO_FETCH_POLICY は (network-only-S) / NODE_ENVは(development) / Server or Client は(Server)
control + c でいちどプロセスを終了してから再度起動します
npm run dev
引用 : 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>
}