Next.js チュートリアルやってみた①
初めに
今回の記事はNext.jsのチュートリアルの1章から5章までの記事です。
1章 導入
npm install -g pnpm
を実行
上記はpnpmを使いたい方向けです。
yarnでもnpmでも良いなら、実行不要です。
npx create-next-app@latest nextjs-dashboard --example "https://github.com/vercel/next-learn/tree/main/dashboard/starter-example" --use-pnpm
を実行
yarnでもnpmを使用する方は最後の --use-pnpm
は消してから実行してください。
チームによって多少変化するとは思いますが、vercelチュートリアルにおけるフォルダの役割は下記の通りです。
-
/app
アプリケーションのすべてのルート、コンポーネント、ロジックが含まれており、主にここで作業を行います。 -
/app/lib
再利用可能なユーティリティ関数やデータ取得関数など、アプリケーションで使用される関数が含まれます。 -
/app/ui
カード、テーブル、フォームなど、アプリケーションのすべての UI コンポーネントが含まれています。時間を節約するために、これらのコンポーネントは事前にスタイル設定されています。 -
/public
画像など、アプリケーションのすべての静的アセットが含まれます。 -
構成ファイル
アプリケーションのルートには、next.config.js
などの構成ファイルもあります。 これらのファイルのほとんどは、create-next-app
を使用して新しいプロジェクトを開始するときに作成され、事前構成されています。
2章 CSS スタイル
tailwindとCSS moduleに関しては一般的なので割愛します。
状態に応じでstyleを変更するclsxを軽く紹介します。
正直、三項演算子でも良いんじゃないと言われたら好みの問題かな…
'inline-flex items-center rounded-full px-2 py-1 text-xs'は共通部分です。
return (
<span
className={clsx(
'inline-flex items-center rounded-full px-2 py-1 text-xs',
{
'bg-gray-100 text-gray-500': status === 'pending',
'bg-green-500 text-white': status === 'paid',
},
)}
>
{status === 'pending' ? (
<>
Pending
<ClockIcon className="ml-1 w-4 text-gray-500" />
</>
) : null}
{status === 'paid' ? (
<>
Paid
<CheckIcon className="ml-1 w-4 text-white" />
</>
) : null}
</span>
);
3章 フォントと画像の最適化
プロジェクトでカスタム フォントを使用すると、フォント ファイルを取得して読み込む必要がある場合にパフォーマンスに影響する可能性があります。
下記のサイトはGoogle がウェブサイトのパフォーマンスとユーザー エクスペリエンスを評価するために使用する指標です。
フォントの場合、レイアウト シフトは、ブラウザが最初にフォールバック フォントまたはシステム フォントでテキストをレンダリングし、読み込んだ後にカスタム フォントに置き換えるときに発生します。Next.js は、next/font
モジュールを使用すると、アプリケーション内のフォントを自動的に最適化します。
import { Inter, Lusitana } from 'next/font/google';
export const inter = Inter({ subsets: ['latin'] });
export const lusitana = Lusitana({
weight: ['400', '700'],
subsets: ['latin'],
});
subsets
とはこれだけ欲しいと宣言するもの
Inter({ subsets: ['latin'] })
ではInter
の中からlatin
だけ取り出して使用するということです。
import '@/app/ui/global.css';
import { inter } from '@/app/ui/fonts';
export default function RootLayout({
children,
}: {
children: React.ReactNode;
}) {
return (
<html lang="en">
<body className={`${inter.className} antialiased`}>{children}</body>
</html>
);
}
antialiased
はフォントを滑らかにするclassです
特定の箇所にのみフォントを当てたい場合、下記のように記載します
import { lusitana } from '@/app/ui/fonts';
<p
className={${lusitana.className} text-xl text-gray-800 md:text-3xl md:leading-normal}
>
コンポーネント<Image>
は HTML タグの拡張であり<img>
、次のような自動画像最適化機能が付属しています。
- 画像の読み込み時にレイアウトシフトが自動的に発生するのを防ぎます
- 大きな画像がビューポートの小さいデバイスに送信されないように、画像のサイズを変更します
- デフォルトでは画像を遅延読み込みします (画像はビューポートに入ると読み込まれます)。
- PNGやJPEGなどの形式の画像を自動でWebPやAVIFにしてくれる
WebPやAVIFで提供した方が画像サイズが小さくなるので、ユーザーに早く届くようになる
画像最適化についての設定はnext.config.js
のimages
プロパティで行います。
ここでの設定はプロジェクト内のすべてのnext/image
コンポーネントに反映されるイメージです。
import Image from 'next/image';
<Image
src="/hero-desktop.png"
width={1000}
height={760}
className="hidden md:block"
alt="Screenshots of the dashboard project showing desktop version"
/>
public配下に画像がある場合、パスを記入しなくて良いので
特に理由がない限り、public配下に画像は配置した方がいいですね。
public ディレクトリ下に配置した画像のパス文字列を渡すことを "remote images”と呼びます。
また<Image>
の別の方法もお伝えします。
StaticI import(local images)と呼ばれる物です。
import Image from 'next/image'
import profilePic from '../public/me.png'
export default function Page() {
return (
<Image
src={profilePic}
alt="Picture of the author"
// width={500} automatically provided
// height={500} automatically provided
// blurDataURL="data:..." automatically provided
// placeholder="blur" // Optional blur-up while loading
/>
)
}
widthとheightは自動的に決定されます。(アスペクト比が崩れない)
また、blurDataURLも自動で設定され、placeholderをblurに設定するだけでプレースホルダーが表示されるようになります。
4章 レイアウトとページの作成
Next.js は、フォルダーを使用してネストされたルートを作成するファイル システム ルーティングを使用します。各フォルダーは、URL セグメントにマップされるルート セグメントを表します。
page.tsxは、React コンポーネントをエクスポートする特別な Next.js ファイルであり、ルートにアクセスするために必要です。
ちなみに特定のフォルダでしか使用しないコンポーネントがある場合、
componentsフォルダにまとめずに、特定のフォルダ配下に_componentsのようなフォルダを作成し、その中でcomponentを定義する方が管理が楽です。
仮にダッシュボードに複数のページで共有されるナビゲーションが合った場合、
Next.js では、特別なlayout.tsx
ファイルを使用して、複数のページで共有される UI を作成できます。
import SideNav from '@/app/ui/dashboard/sidenav';
export default function Layout({ children }: { children: React.ReactNode }) {
return (
<div className="flex h-screen flex-col md:flex-row md:overflow-hidden">
<div className="w-full flex-none md:w-64">
<SideNav />
</div>
<div className="flex-grow p-6 md:overflow-y-auto md:p-12">{children}</div>
</div>
);
}
このようにした場合、SideNav
はダッシュボード配下の全てのページに表示されます。
{children}にはダッシュボード配下のそれぞれのフォルダのpage.tsxの内容が表示されます。
Next.js でレイアウトを使用する利点の 1 つは、ナビゲーション時にページ コンポーネントのみが更新され、レイアウトは再レンダリングされないことです。これは部分レンダリングと呼ばれます。
5章 ページ間の移動
Next.js では、<Link />コンポーネントを使用してアプリケーション内のページ間をリンクできます。
import {
UserGroupIcon,
HomeIcon,
DocumentDuplicateIcon,
} from '@heroicons/react/24/outline';
import Link from 'next/link';
export default function NavLinks() {
return (
<>
{links.map((link) => {
const LinkIcon = link.icon;
return (
<Link
key={link.name}
href={link.href}
className="flex h-[48px] grow items-center justify-center gap-2 rounded-md bg-gray-50 p-3 text-sm font-medium hover:bg-sky-100 hover:text-blue-600 md:flex-none md:justify-start md:p-2 md:px-3"
>
<LinkIcon className="w-6" />
<p className="hidden md:block">{link.name}</p>
</Link>
);
})}
</>
);
}
Next.jsはルートセグメントごとにアプリケーションを自動的にコード分割します。
コードをルートごとに分割すると、ページが分離されます。
さらに特定のページでエラーが発生しても、アプリケーションの残りの部分は引き続き動作します。
本番環境では、<Link />
コンポーネントがブラウザのビューポートに表示されるたびに、Next.js はリンクされたルートのコードをバックグラウンドで自動的にプリフェッチします
ユーザーがリンクをクリックするまでに、リンク先ページのコードがバックグラウンドですでに読み込まれているため、ページ遷移がほぼ瞬時に行われます。
ちなみにプリフェッチではバックエンドにリクエストを送るので、vercelにデプロイした場合
サーバーレスファンクションの機能を使ってしまうので、お金がかかります。
皆さん大好きAmazonでプリフェッチをした場合、とてつもない金額がかかりそうですね…
プリフェッチを多用するのもよくないですね…
ちなみにプリフェッチはオフに設定することもできます。
その場合、Home、Invoices、Customersそれぞれにホバーした際にリンク先の情報を取得するので、過度な請求がくることも無さそうです。
一般的な UI パターンは、アクティブ リンクを表示して、ユーザーが現在どのページにいるかを示すことです。
これを行うには、URL からユーザーの現在のパスを取得する必要があります。
Next.js には、usePathname()
パスを確認してこのパターンを実装するために使用できる というフックが用意されています。
const pathname = usePathname();
でパスの名前(http://localhost:3000/dashboard/invoices)を取得します
pathname === link.href
ならbg-sky-100 text-blue-600
を当てるように記載します。
'use client';
import {
UserGroupIcon,
HomeIcon,
DocumentDuplicateIcon,
} from '@heroicons/react/24/outline';
import Link from 'next/link';
import { usePathname } from 'next/navigation';
import clsx from 'clsx';
const links = [
{ name: 'Home', href: '/dashboard', icon: HomeIcon },
{ name: 'Invoices', href: '/dashboard/invoices', icon: DocumentDuplicateIcon,},
{ name: 'Customers', href: '/dashboard/customers', icon: UserGroupIcon },
];
export default function NavLinks() {
const pathname = usePathname();
return (
<>
{links.map((link) => {
const LinkIcon = link.icon;
return (
<Link
key={link.name}
href={link.href}
className={clsx(
'flex h-[48px] grow items-center justify-center gap-2 rounded-md bg-gray-50 p-3 text-sm font-medium hover:bg-sky-100 hover:text-blue-600 md:flex-none md:justify-start md:p-2 md:px-3',
{
'bg-sky-100 text-blue-600': pathname === link.href,
},
)}
>
<LinkIcon className="w-6" />
<p className="hidden md:block">{link.name}</p>
</Link>
);
})}
</>
);
}
一旦終了
拙い記事を最後までご覧いただき、誠にありがとうございます。
この続きはまた書こうと思いますので、立ち寄っていただけると幸いです。
Discussion