TypescriptをGoに変換して覚えてみる

はじめに

こんにちわ!こんばんわ!ご観覧ありがとうございます。
Golangに興味があるので、普段使っているTypescriptで行う処理をどのように描くことができるのか、覚えるために記録してみます。

Golangとは

Golang、またはGoはGoogleが開発した静的型付けされたコンパイル言語です。Goはシンプルさと効率性を重視して設計されており、ソフトウェアの開発を迅速かつ効率的に行うことができる。
また、Goは並行処理をサポートしており、複数のタスクを同時に実行することが可能である。

go.dev

早速はじめてみる

TypescriptのTypeの定義を変換:

Typescript

type TypeName = {
  name: string;
  age: number;
  is_member: boolean;
};

Golang

type TypeName struct {
   Name     string
   Age      int
   IsMember bool
}
  • オブジェクトの場合、structをつける
  • :(カンマ)と;(セミコロン)が不要
  • stringは同じ、number -> int, boolean -> boolになる(まだまだありそうですが)

map関数の代用:

Typescript

let numbers = [1, 2, 3, 4, 5];
let squares = numbers.map(num => num * num);

Golang

numbers := []int{1, 2, 3, 4, 5}
squares := make([]int, len(numbers))
for i, num := range numbers {
    squares[i] = num * num
}
  • Golangでは、map関数の代わりにforループを使用する。
  • 配列の定義方法がint(Typeです!){1,2,3,4,5}(実際の中身です!)
    なんだか見慣れないmakeだのrangeだの出てきてるので、友達に聞いてみたので以下にまとめます。

numbers := []int{1, 2, 3, 4, 5}: これはGoで整数のスライスを作成しています。:=は変数宣言と同時に初期化を行うためのショートハンドです。
squares := make([]int, len(numbers)): make関数は組み込み関数で、スライスやマップ、チャネルなどの動的サイズの型を初期化します。ここでは、numbersと同じ長さの整数型のスライスを作成しています。
for i, num := range numbers: rangeキーワードはGoのforループで使用され、スライスやマップを反復処理します。rangeはインデックスと値の両方を返します。ここでは、iはインデックス(0から始まる)で、numはそのインデックスに対応する値です。
squares[i] = num * num: これは各要素を二乗して、結果をsquaresスライスの対応する位置に格納しています。

ExportとImportについて:

Export

Typescript

export const exportFunc = () => {};

Golangでは、大文字で始まる名前(関数、型、変数など)はエクスポートされ、パッケージ外からアクセス可能である。
小文字で始まる名前はエクスポートされず、同じパッケージ内からのみアクセス可能である。
Golang

package main

// Exported function
func SayHello() {
    fmt.Println("Hello, world!")
}

// Unexported function
func sayGoodbye() {
    fmt.Println("Goodbye, world!")
}

上記の例では、SayHello関数は大文字で始まるためエクスポートされ、パッケージ外からアクセス可能ですが、sayGoodbye関数は小文字で始まるためエクスポートされず、同じパッケージ内からのみアクセス可能となる。

Import:

Typescript

import importFunc from '../import'

Golangでは、importステートメントを使用して行います。例えば、fmtパッケージをインポートするには次のようにする

package main

import "fmt"

func main() {
    fmt.Println("Hello, world!")
}

上記の例では、fmtパッケージがインポートされており、その中のPrintln関数が使用できる。

AppRouterとSuspenseについて

はじめに

こんにちわ!こんばんわ!ご観覧ありがとうございます。
定期的に雑色な記事を投稿します。(タイトルは記事のメインの内容にしてますが)
基本的には抱えている問題を改善し、改善方法を記事にしてアップしようと考えています。

AppRouterとSuspenseについて

AppRouterのとSuspenseの組み合わせを使ったことなかったので、試しに実装してみます。
出オチすぎますが、以下記事が個人的にめちゃくちゃわかりやすかったです。

qiita.com

AppRouter

AppRouterについては以前の記事でつらつら書いているので、そちらを参考にしてもらえたら幸いです。

Suspense

SuspenseとはReact18から追加された機能で、子コンポーネントの読み込みを完了するまでフォールバックを表示できます。

<Suspense fallback={<Loading />}>
  <SomeComponent />
</Suspense>

簡単に表現すると、子のコンポーネントサスペンド(一時停止)した際に、フォールバックするので、データの取得を担当するコンポーネントがデータ取得までの間、Loading画面を出せるというものです。
trycatch分を書くのと似ています。

import { Suspense } from 'react';
import Albums from './Albums.js';
import Biography from './Biography.js';
import Panel from './Panel.js';

export default function ArtistPage({ artist }) {
  return (
    <>
      <h1>{artist.name}</h1>
      <Suspense fallback={<Loading />}>
        <Biography artistId={artist.id} />
        <Panel>
          <Albums artistId={artist.id} />
        </Panel>
      </Suspense>
    </>
  );
}

function Loading() {
  return <h2>🌀 Loading...</h2>;
}

これは公式のSuspenseの使用例ですが、子コンポーネントは複数可能で、どれか一つでもサスペンドした場合はフォールバックとなります。

AppRouterとの組み合わせ

とは言っても、使い方は上記で説明した通りです。

import { Suspense } from "react";
import { ChildrenServerComponent } from "@/components/ChildrenServerComponent";
import { Squares } from "@/components/Squares";
import Link from "next/link";
import { Loading } from "@/components/Loading";

export default async function Home() {

  return (
    <div className="p-8 relative">
      <h1 className="text-gradient-blue-green" data-content="TopPage">
        TopPage
      </h1>
      <Link href="/sample" className="text-sky-600">
        Sampleページへ
      </Link>
      <Suspense fallback={<Loading />}>
        <ChildrenServerComponent />
      </Suspense>
      <Squares />
    </div>
  );
}

ChildrenServerComponentがサスペンドしている時はフォールバックされるので、Loadingが出ます。

デザイン

紹介になりますが、どの記事も淡白UIの紹介が多かったので、以下記事は確かに!と参考になりました。

qiita.com

終わりに

実際に色々と使ってみて、慣れないとなという感じです。
ここまでみていただきありがとうございました!

Nextjs AppRouter個人的気になる点をまとめる

はじめに

こんにちわ!こんばんわ!ご観覧ありがとうございます。
定期的に雑色な記事を投稿します。(タイトルは記事のメインの内容にしてますが)
基本的には抱えている問題を改善し、改善方法を記事にしてアップしようと考えています。

今回の内容

AppRouterの気になった機能をつらつら書いていきます。

前回の内容

前回記事

パフォーマンス無関心で作ったサイトのパフォーマンスチューニング番外編 Nextjs AppRouter(色々試す2) - zare926のブログ

NextjsのAppRouterのRoute Handlers(API)ついて書きました。

SEOタグの設定

import type { Metadata } from 'next'
 
export const metadata: Metadata = {
  title: 'タイトルです!',
  description: '説明です!',
}

PageRouterの時は以下の方法でLighthouseが100点を出すそうです。

kiyobl.com

これはAppRouterいい仕事してますね。

何回も同じFetchすな!(Request Memoization)

簡単にいうと親子で同じFetchをしていたら1個にまとめてくれます!
1回しかFetchせず、データを使いまわしてくれます。
これはちょっと前にcacheを調べた時に起きてましたね。
確か、fetchの設定は親が優先されたはずです。
とりあえず優しい

Server Actions

まず、stableな機能ではないので注意です。

import db from './db';
import { redirect } from 'next/navigation';
 
async function create(formData: FormData) {
  'use server';
  const post = await db.post.insert({
    title: formData.get('title'),
    content: formData.get('content'),
  });
  redirect(`/blog/${post.slug}`);
}
 
export default function Page() {
  return (
    <form action={create}>
      <input type="text" name="title" />
      <textarea name="content" />
      <button type="submit">Submit</button>
    </form>
  );
}

//https://nextjs.org/blog/next-13-4#server-actions-alpha

簡潔にまとめると、APIで実装したいCreate処理などを直接書き込めちゃいます。

 'use server';

こいつが肝です
関数が複数ある場合は、ファイルの一番上に定義することも可能です。
てことはクライアントサイドには、見えないコードができるはずなので、見せたくないものを見せないようになどの使い方もできそうです。
fetch関数も使わなくてすみます。
一応設定が必要
next.config.js

module.exports = {
  experimental: {
    serverActions: true,
  },
}

サーバーコンポーネントでは、UIコンポーネントが使えない!!

MUIやChakra UIが使えません!
サーバーでは動かせないemotionを使っているからだそうです。
クライアントコンポーネントで使用することはできます。
Chakra UIでこうしろと書いてありました。

chakra-ui.com

最後に

調べて気になったのはこの辺りでしたね。
統括してまとめてるサイトは結構あるんですが、メモして自分も記憶出るようにと書いてみました。
ここまで読んでいただいてありがとうございました!!

パフォーマンス無関心で作ったサイトのパフォーマンスチューニング番外編 Nextjs AppRouter(色々試す2)

はじめに

こんにちわ!こんばんわ!ご観覧ありがとうございます。
定期的に雑色な記事を投稿します。(タイトルは記事のメインの内容にしてますが)
基本的には抱えている問題を改善し、改善方法を記事にしてアップしようと考えています。

お題と経緯

NextjsのPageRouterからAppRouterに乗り換えてみてパフォーマンスを測ってみたいし、使ってもみたいなってことで、
今回はFirebaseのhostingでAppRouterがどこまで機能するかを、パフォーマンス向上の一環として試そうと思います!
まずはAppRouterでサンプルのAppを作成しつつ、備忘録で説明を書いていこうと思います。

今回の内容

AppRouterのRoute Handlers(API)について調べていきたいと思います!

Routing: Route Handlers | Next.js

使用した技術

Category     Technology Stack
Frontend Nextjs,React,Typescript
Backend Typescript,Node,CloudFunction
Database Firestore
Design   XD  

前回の内容

前回記事

パフォーマンス無関心で作ったサイトのパフォーマンスチューニング番外編 Nextjs AppRouter(色々試す) - zare926のブログ

NextjsのAppRouterでサーバーコンポーネントやクライアントコンポーネントについて書きました。

Route Handlersとは

Nextjsの13.4からstableとなったAppRouterで実装できるAPIですね。
PagesRouterの時はAPI Routesでしたね。

APIRouteとRoute Handlersの違い

一つ目はファイルパスですね。

API Routes

pages/api/sample-api.ts
pages/api/[slug].ts

export default function handler(req, res) {
  res.status(200).json({ name: 'Michelle' });
}

Route Handlers

app/sample-api/route.ts
app/sample-api/[slug]/route.ts

import { NextResponse } from "next/server";

export async function GET() {
  return NextResponse.json({ name: 'Michelle' });
}

ざっくりとこの様な変更があるようですね。
実際にはAPI Routesではreq.queryなどURLのパラメーターを展開できたりしましたが、どの様に変わるのかを調べたいと思います。

実際に作成してみる

app/[slug]/route.ts

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

export async function GET(
  request: NextRequest,
  { params }: { params: { slug: string } }
) {
  console.log(params);
  console.log(request);
  return NextResponse.json({ data: new Date().toISOString() });
}

こんな感じでGETはrequestとparamsを受け取れる様ですので、中身を見てみます。
[slug]の部分は以下にします。

await fetch("/slug-url");

わかりづらいですが、{ slug: 'slug-url' }console.log(params);で、それ以下がconsole.log(request);ですね。
pathはこの方法で取れますね。

URLのクエリパラメーターの取り方も変わってます。

    const params = {
      hoge: "1234567",
      fuga: String(76454321),
    };
    const parameter = new URLSearchParams(params);
    await fetch(`/slug-url?${parameter}`);

API

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

export async function GET(
  request: NextRequest,
  { params }: { params: { slug: string } }
) {
  console.log(params);
  const searchParams = request.nextUrl.searchParams;
  const hoge = searchParams.get("hoge");
  console.log(hoge); // 1234567とログに出る。
  const fuga = searchParams.get("fuga");
  console.log(fuga); // 76454321とログに出る。
  return NextResponse.json({ data: new Date().toISOString() });
}

もちろんrequest.bodyもあります。
クライアントサイドから動的な値でAPIの実行内容を変えたい場合も、PagesRouter同様にできますね。

最後に

変更点は特に違和感なく使えたかなと思います。
paramsの取得はPagesRouterの方が楽でしたね。
AppRouterの場合、searchParams.get()後の型推論string | nullだったので、req.queryから抜き取るよりも型がキッチリしてますね。
ファイル名もpages.ts同様、決まりがあるので頭を少しでも使わ内容にできる点は満足です!
ここまで読んでいただきありがとうございました!

パフォーマンス無関心で作ったサイトのパフォーマンスチューニング番外編 Nextjs AppRouter(色々試す)

はじめに

こんにちわ!こんばんわ!ご観覧ありがとうございます。
定期的に雑色な記事を投稿します。(タイトルは記事のメインの内容にしてますが)
基本的には抱えている問題を改善し、改善方法を記事にしてアップしようと考えています。

お題と経緯

NextjsのPageRouterからAppRouterに乗り換えてみてパフォーマンスを測ってみたいし、使ってもみたいなってことで、
今回はFirebaseのhostingでAppRouterがどこまで機能するかを、パフォーマンス向上の一環として試そうと思います!
まずはAppRouterでサンプルのAppを作成しつつ、備忘録で説明を書いていこうと思います。

今回の内容

画像の圧縮方法を記録していきたいと思います!

使用した技術

Category     Technology Stack
Frontend Nextjs,React,Typescript
Backend Typescript,Node,CloudFunction
Database Firestore
Design   XD  

前回の内容

前回記事

パフォーマンス無関心で作ったサイトのパフォーマンスチューニング番外編 Nextjs AppRouter(fetch) - zare926のブログ

NextjsのAppRouterでSGやSSR、ISRのfetchについてやってみました。

色々試す!

今回は色々試す回にします。
まずはクライアントコンポーネントから動的なfetchをしてみます。
FetchComponentとButtonのコンポーネントを作成

FetchComponent.tsx

"use client";
import React, { useState } from "react";
import { Button } from "./Button";

const FetchComponent = () => {
  const [user, setUser] = useState<{ id: number; name: string }>();
  const testFunc = async () => {
    const number = Math.floor(Math.random() * (10 - 1 + 1)) + 1;
    const response = await fetch(
      `https://jsonplaceholder.typicode.com/users/${number}`
    );
    const user: { id: number; name: string } = await response.json();
    return user;
  };
  const fetchUser = async () => {
    const response = await testFunc();

    setUser(response);
  };
  return (
    <div>
      <Button text="fetch button" onClick={fetchUser} />
      <p>
        クライアントコンポーネントからfetchした値 :
        {user && <span>{user.name}</span>}
      </p>
    </div>
  );
};

export default FetchComponent;

Button.tsx

"use client";
import React from "react";

type Props = {
  text: string;
  onClick?: () => void;
};

export const Button = (props: Props) => {
  const { text, onClick } = props;
  return (
    <button
      className="bg-gray-200 hover:bg-gray-300 active:bg-gray-400 border border-gray-400  p-2 transition-all duration-300 ease-in-out"
      onClick={() => {
        onClick && onClick();
      }}
    >
      {text}
    </button>
  );
};

このコンポーネントをルートのpage.tsxに組み込む

import { FetchComponent } from "@/components/FetchComponent";
import { Squares } from "@/components/Squares";
import Link from "next/link";

export default async function Home() {
  const response = await fetch(`http://localhost:3000/sample-date-get`, {
    next: { revalidate: 5 },
  });
  const data = await response.json();

  return (
    <div className="p-8 relative">
      <h1 className="text-gradient-blue-green" data-content="TopPage">
        TopPage
      </h1>
      <Link href="/sample" className="text-sky-600">
        Sampleページへ
      </Link>
      <p>{data.data}</p>
      <FetchComponent />
      <Squares />
    </div>
  );
}

json placeholderでuserの名前を引っ張ってきてますが、うまく動いていますね。

ちなみにクライアントサイドでのfetch関数にISRの設定をしてもcacheはされなかったです。 caches APIなどクライアントサイドでは、他の方法でcacheする必要がありそうですね。

次にサーバーコンポーネントからfetchをしてcacheの設定をしてみます。

ChildrenServerComponent.tsx

import React from "react";

export const ChildrenServerComponent = async () => {
  const response = await fetch(`http://localhost:3000/sample-date-get`, {
    next: { revalidate: 20 },
  });
  const data = await response.json();

  return (
    <div>
      <p>
        サーバーサイドコンポーネントからfetchした値 :<span>{data.data}</span>
      </p>
    </div>
  );
};

page.tsx

import { ChildrenServerComponent } from "@/components/ChildrenServerComponent";
// import { FetchComponent } from "@/components/FetchComponent";
import { Squares } from "@/components/Squares";
import Link from "next/link";

export default async function Home() {
  const response = await fetch(`http://localhost:3000/sample-date-get`, {
    next: { revalidate: 5 },
  });
  const data = await response.json();

  return (
    <div className="p-8 relative">
      <h1 className="text-gradient-blue-green" data-content="TopPage">
        TopPage
      </h1>
      <Link href="/sample" className="text-sky-600">
        Sampleページへ
      </Link>
      <p>{data.data}</p>
      <ChildrenServerComponent />
      <Squares />
    </div>
  );
}

しっかりとサーバーでfetchされ表示されました。

親のサーバーコンポーネントではrevalidateを5秒に設定し、子のサーバーコンポーネントでは20秒にしましたが、同じAPIだったからなのか、親の5秒に引っ張られました。
逆に親の5秒をコメントアウトしたところ、親を含めて20秒の設定になりました。
APIを分けた場合、それぞれの設定が生かされました。

てとこですかね。

最後にクライアントコンポーネントの子にサーバーコンポーネントを設置してみます。

FetchComponent.tsx

"use client";
import React, { useState } from "react";
import { Button } from "./Button";
import { ChildrenServerComponent } from "./ChildrenServerComponent";

export const FetchComponent = () => {
  const [user, setUser] = useState<{ id: number; name: string }>();
  const testFunc = async () => {
    const number = Math.floor(Math.random() * (10 - 1 + 1)) + 1;
    const response = await fetch(
      `https://jsonplaceholder.typicode.com/users/${number}`
    );
    const user: { id: number; name: string } = await response.json();
    return user;
  };
  const fetchUser = async () => {
    const response = await testFunc();

    setUser(response);
  };
  return (
    <div> 
      <Button text="fetch button" onClick={fetchUser} />
      <p>
        クライアントコンポーネントからfetchした値 :
        {user && <span>{user.name}</span>}
      </p>
      <ChildrenServerComponent />
    </div>
  );
};

page.tsx

import { FetchComponent } from "@/components/FetchComponent";
import { Squares } from "@/components/Squares";
import Link from "next/link";

export default async function Home() {
  const response = await fetch(`http://localhost:3000/sample-date-get`, {
    next: { revalidate: 5 },
  });
  const data = await response.json();

  return (
    <div className="p-8 relative">
      <h1 className="text-gradient-blue-green" data-content="TopPage">
        TopPage
      </h1>
      <Link href="/sample" className="text-sky-600">
        Sampleページへ
      </Link>
      <p>{data.data}</p>
      <FetchComponent />
      <Squares />
    </div>
  );
}

SC<CS<SC(async/await有)という設定ですが、結論これはダメでした。

クライアントコンポーネントは、コンポーネント単位でasync/awaitを使えないので、子のコンポーネントがサーバーサイドでasync/awaitを使用しているとエラーが出ます。
サーバーコンポーネントがasync/awaitを使っていなければ、今回の様な親子関係も可能です。

ちなみにSC<SC<CSは問題なく動きます。
この辺りは慣れが必要そうですね!

最後に

色々と把握できた回で満足でした!
ちなみに今回出たエラーを翻訳すると

エラー:async/awaitはクライアント・コンポーネントではまだサポートされていません。このエラーは、本来サーバ用に書かれたモジュールに誤って `'use client'` を追加してしまった場合によく起こります。

なんですが、将来的に対応する様になるんですかね。
そうするとかなり自由度があるようなないような。。。
動的なアプリを作る場合は結構考えものです。

ここまでみていただいてありがとうございました!!!

パフォーマンス無関心で作ったサイトのパフォーマンスチューニング番外編 Nextjs AppRouter(fetch)

はじめに

こんにちわ!こんばんわ!ご観覧ありがとうございます。
定期的に雑色な記事を投稿します。(タイトルは記事のメインの内容にしてますが)
基本的には抱えている問題を改善し、改善方法を記事にしてアップしようと考えています。

お題と経緯

NextjsのPageRouterからAppRouterに乗り換えてみてパフォーマンスを測ってみたいし、使ってもみたいなってことで、
今回はFirebaseのhostingでAppRouterがどこまで機能するかを、パフォーマンス向上の一環として試そうと思います!
まずはAppRouterでサンプルのAppを作成しつつ、備忘録で説明を書いていこうと思います。

今回の内容

AppRouterで色々なfetchしていきたいと思います!

使用した技術

Category     Technology Stack
Frontend Nextjs,React,Typescript
Backend Typescript,Node,CloudFunction
Database Firestore
Design   XD  

前回の内容

前回記事

パフォーマンス無関心で作ったサイトのパフォーマンスチューニング番外編 Nextjs AppRouter - zare926のブログ

NextjsのAppRouterで静的コンテンツを表示するところまでやってみました。

AppRouterでfetchする

SGやだとgetStaticProps、SSRだとgetServerSidePropsを定義し、propsでJSXに値を渡していましたが、AppRouterのfetchはJSX内に直接書くことができます。

import { Squares } from "@/components/Squares";
import Link from "next/link";

export default async function Home() {
  const response = await fetch("https://jsonplaceholder.typicode.com/users/1"); // ここ
  const data: { name: string } = await response.json();

  return (
    <div className="p-8 relative">
      <h1 className="text-gradient-blue-green" data-content="TopPage">
        TopPage
      </h1>
      <Link href="/sample" className="text-sky-600">
        Sampleページへ
      </Link>
      <p>{data.name}</p> // 定義したデータをそのまま使える
      <Squares />
    </div>
  );
}

バケツリレーがなくなりますね。
ちなみにこの実装だとSGになり、ビルド時に1回だけfetchをしてくれます。

サーバーコンポーネントでSG,SSR,ISRの書き方

SSRの場合は以下の様に書きます。

import { Squares } from "@/components/Squares";
import Link from "next/link";

export const fetchCache = "default-no-store";

export default async function Home() {
  const number = Math.floor(Math.random() * (10 - 1 + 1)) + 1;
  const response = await fetch(
    `https://jsonplaceholder.typicode.com/users/${number}`
  );
  const data: { name: string } = await response.json();
  return (
    <div className="p-8 relative">
      <h1 className="text-gradient-blue-green" data-content="TopPage">
        TopPage
      </h1>
      <Link href="/sample" className="text-sky-600">
        Sampleページへ
      </Link>
      <p>{data.name}</p>
      <Squares />
    </div>
  );
}

build後もサーバーサイドでやり取りしてくれるので、動的な表示が可能になります。
他にも、Dynamic Functionというものがあり、Server Component 内で使える cookies(), headers()のことを指します。
cookies(), headers()を定義するとSSRになります。

import { Squares } from "@/components/Squares";
import Link from "next/link";
import { cookies } from "next/headers";

// export const fetchCache = "default-no-store";

export default async function Home() {
  const cookieStore = cookies();
  const number = Math.floor(Math.random() * (10 - 1 + 1)) + 1;
  const response = await fetch(
    `https://jsonplaceholder.typicode.com/users/${number}`
  );
  const data: { name: string } = await response.json();

  return (
    <div className="p-8 relative">
      <h1 className="text-gradient-blue-green" data-content="TopPage">
        TopPage
      </h1>
      <Link href="/sample" className="text-sky-600">
        Sampleページへ
      </Link>
      <p>{data.name}</p>
      <Squares />
    </div>
  );
}

ISRの説明をするためにapiを用意。
src/app/sample-date-get/route.ts

import { NextResponse } from "next/server";

export async function GET() {
  return NextResponse.json({ data: new Date().toISOString() });
}

時間でCacheの設定をするのでわかりやすいAPIを準備します。

import { Squares } from "@/components/Squares";
import Link from "next/link";

export const revalidate = 10; // ここ

export default async function Home() {
  const response = await fetch(`http://localhost:3000/sample-date-get`);
  const data = await response.json();

  return (
    <div className="p-8 relative">
      <h1 className="text-gradient-blue-green" data-content="TopPage">
        TopPage
      </h1>
      <Link href="/sample" className="text-sky-600">
        Sampleページへ
      </Link>
      <p>{data.data}</p>
      <Squares />
    </div>
  );
}

上記の場合はコンポーネント全体で10秒キャッシュとしています。
もしくはfetch関数単位でも設定可能です。

  const response = await fetch(`http://localhost:3000/sample-date-get`, {
    next: { revalidate: 10 },
  });

こちらに関してyarn devだとうまくいきますが、yarn buildだとうまくいきません・・・、原因がわかったら記事に追加しておきます。
.next/cache/fetch-cache ここにcacheで下記の様にjsonができているので、機能して欲しいのですが・・・

"status":200,"url":"http://localhost:3000/sample-date-get"},"revalidate":10,"tags":[]}

最後に

少しクセはありますが、cacheが楽に書けるなと思いました。
またページによってSG、ISRなど機能を振り分けしやすいと感じます。
ただISRだけはbuild後、期待通りのcacheではないので、調べないと・・・
果たしてFirebaseでhostingはできるのか・・・?
次回はクライアントコンポーネントから動的な関数を呼ぶ方法を書いてみたいと思ってます。
ここまでみていただきありがとうございました!!!

パフォーマンス無関心で作ったサイトのパフォーマンスチューニング番外編 Nextjs AppRouter

はじめに

こんにちわ!こんばんわ!ご観覧ありがとうございます。
定期的に雑色な記事を投稿します。(タイトルは記事のメインの内容にしてますが)
基本的には抱えている問題を改善し、改善方法を記事にしてアップしようと考えています。

お題と経緯

NextjsのPageRouterからAppRouterに乗り換えてみてパフォーマンスを測ってみたいし、使ってもみたいなってことで、
今回はFirebaseのhostingでAppRouterがどこまで機能するかを、パフォーマンス向上の一環として試そうと思います!
まずはAppRouterでサンプルのAppを作成しつつ、備忘録で説明を書いていこうと思います。

今回の内容

AppRouterについて調べていきたいと思います!

使用した技術

Category     Technology Stack
Frontend Nextjs,React,Typescript
Backend Typescript,Node,CloudFunction
Database Firestore
Design   XD  

前回の内容

前回記事

パフォーマンス無関心で作ったサイトのパフォーマンスチューニング④ 画像のアップロード圧縮 - zare926のブログ

画像アップロード時の圧縮方法でした!

Nextjs AppRouterとは

簡単にいうとサーバーサイドでコンポーネントレンダリングをして、HTMLを返してくれる(全部じゃない)ルーティングのベース機能です。
PageRouterからどのように変わったのかは以下記事がわかりやすかったです。

ざっくりApp Router入門【Next.js】

init

$  npx create-next-app@latest
Need to install the following packages:
  create-next-app@13.5.2
Ok to proceed? (y)
✔ What is your project named? … app-router-sample-app
✔ Would you like to use TypeScript? … No / Yes
✔ Would you like to use ESLint? … No / Yes
✔ Would you like to use Tailwind CSS? … No / Yes
✔ Would you like to use `src/` directory? … No / Yes
✔ Would you like to use App Router? (recommended) … No / Yes
✔ Would you like to customize the default import alias? … No / Yes

$ cd app-router-sample-app
$ code. // 設定していればvscode起動

触っていく!

PageRouterはpageディレクトリの中でルーティングさせてましたが、AppRouterはappディレクトリの中でルーティングをさせます。
またPageRouterからの変更点として以下のようなディレクトリ名かつファイル名である必要があります。

.
├── app
│   ├── favicon.ico
│   ├── globals.css
│   ├── layout.tsx
│   ├── page.tsx
│   └── sample
│       └── page.tsx
└── components
    └── Squares.tsx

すでに初期状態からsample/page.tsxとcomponents/Squares.tsxを作成しています。
layout.tsxはPageRouterの_app.tsxの役割って感じですね。
ディレクトリごと作成できるので、仕様を分けたいページがある場合は便利です。(と解釈している)
各ファイルをみましょう。
page.tsx

import { Squares } from "@/components/Squares";
import Link from "next/link";

export default function Home() {
  return (
    <div className="p-8 relative">
      <h1 className="text-gradient-blue-green" data-content="TopPage">
        TopPage
      </h1>
      <Link href="/sample" className="text-sky-600">
        Sampleページへ
      </Link>
      <Squares />
    </div>
  );
}

sample/page.tsx

import { Squares } from "@/components/Squares";
import Link from "next/link";

export default function Home() {
  return (
    <div className="p-[100px]">
      <h1 className="text-gradient-blue-green" data-content="SamplePage">
        SamplePage
      </h1>
      <Link href="/" className="text-sky-600">
        Topページへ
      </Link>
      <Squares />
    </div>
  );
}

components/Squares.tsx

"use client";
import React, { useEffect, useState } from "react";

export const Squares = () => {
  const [squares, setSquares] = useState<
    {
      top: string;
      left: string;
      width: string;
      height: string;
      transform: string;
    }[]
  >([]);

  useEffect(() => {
    const generateRandomSquare = () => ({
      top: `${Math.random() * 50 + 25}%`,
      left: `${Math.random() * 100}%`,
      width: `${Math.random() * 40 + 10}px`,
      height: `${Math.random() * 40 + 10}px`,
      transform: `rotate(${Math.random() * 360}deg)`,
    });

    const initialSquares = Array.from({ length: 100 }, generateRandomSquare);
    setSquares(initialSquares);

    const interval = setInterval(() => {
      const newSquares = initialSquares.map(generateRandomSquare);
      setSquares(newSquares);
    }, 10000);

    return () => clearInterval(interval);
  }, []);

  return (
    <div className="relative h-[1000px]">
      {squares.map((square, index) => (
        <div
          key={index}
          className="border border-black rotate-animation"
          style={{
            position: "absolute",
            top: square.top,
            left: square.left,
            width: square.width,
            height: square.height,
            transform: square.transform,
            transition: "transform 10s, top 10s, left 10s",
          }}
        ></div>
      ))}
    </div>
  );
};

実際の動きはこんな感じ

動きましたね!
ということで説明をしていきます。
まず大事なルーティングですが、ディレクトリ名がurlとなり、直下にpage.tsxを作るようになりました。
/sampleでsample/page.tsxが表示されるということですね。
これらはすべてサーバーコンポーネントとなります。
そしてここがよくひっかかる!サーバーコンポーネントはuseStateが使えません。
使いたい場合は、components/Squares.tsxの一番上に定義した"use client";を設定する必要があります。
これはクライアントコンポーネントですよという宣言になり、このような制約ができているので、最初はひっかかりそう
レンダリングはサーバーからしてしまうので、state更新時にクライアントコンポーネントでないといけないという解釈をしました。

最後に

今回はほぼ準備でしたが、次回以降はサーバーコンポーネント側でfetch関数を扱い、データ取得などを行ってみようと思います。
SGやISRなど切り替わった部分を実装してみて、FirebaseへHostingしてみようと思います。
これがうまくいけば作成しているサイトもAppRouterに切り替え可能なはず!
ちなみにサーバーに負担させるメリット?的ない解説はここに載っていました。

Next.jsの新常識「App Router」を学ぼう! (1/3)|CodeZine(コードジン)

ここまで見ていただいた方ありがとうございました!