5
3

React + TypeScript: Next入門02 イメージとメタデータおよびCSSを扱う

Last updated at Posted at 2023-01-25

Next.jsチュートリアルの公式作例を、TypeScriptも加えて、一からつくってみようというシリーズの第2回です。前回は、トップページを書き替えて、新たに追加した別ページとリンクしてみたというくらいでした。今回から、公式作例に少しずつ近づけてゆきましょう。

Imageコンポーネントでイメージを差し込む

Next.jsはイメージなどの静的素材も扱えます。素材ファイルを置くトップレベルは、publicディレクトリです。Reactコンポーネントを収めるpagesと同じく、アプリケーションのルートから参照できます。

ページに表示するプロフィール画像を用意しましょう。ディレクトリpublicimagesをつくってください。

  • 画像をpublic/images/profile.pngとして収めます。
  • イメージの大きさは幅400px×高さ400pxくらいが適切です。
    • 作例のコードは正方形を前提としています。
  • ひな形プロジェクトにつくられたpublicディレクトリの使わないSVGファイルは削除して構いません。
    • public/favicon.icoは残しておいてください。

<Image>Imageコンポーネントは、イメージの扱いを最適化するために備わりました。Imageコンポーネントの使い方はつぎに示したコード例のとおりです(図001)。widthheightには希望のサイズが与えられます。アスペクト比は元画像の比率を保ってください。

pages/posts/first-post.tsx
import Image from 'next/image';

const FirstPost: FC = () => {
	return (
		<>
			<Image
				src="/images/profile.png" // イメージファイルのルート
				height={144} // イメージサイズの設定
				width={144} // アスペクト比を正しく
				alt="Your Name"
			/>

		</>
	);
};

図001■ページにLinkコンポーネントでプロフィール画像が加わった

qiita_2301001_007.png

Headコンポーネントでメタデータを定める

<title>など<head>要素に加えるページのメタデータをサポートするのがHeadコンポーネント(<Head>)です。モジュールpages/index.tsxHeadコンポーネントをつぎのように加えてみましょう。トップページに<title>とアイコンが表示されたはずです。

pages/index.tsx
import Head from 'next/head';

export default function Home() {
	return (
		<>
			<Head>
				<title>Create Next App</title>
				<link rel="icon" href="/favicon.ico" />
			</Head>

		</>
	);
}

pages/posts/first-post.tsxモジュールにも、Headコンポーネントで<title>をつぎのように定めます。ページを遷移すると、ブラウザのタブに示されるタイトルが切り替わるようになりました。

pages/posts/first-post.tsx
import Head from 'next/head';

const FirstPost: FC = () => {
	return (
		<>
			<Head>
				<title>First Post</title>
			</Head>

		</>
	);
};

LayoutコンポーネントにCSSモジュールのスタイルを加える

プロジェクトにはすでにstyles/globals.cssがあり、pages/_app.tsxに読み込まれて、アプリケーション全体のスタイルを定めていました。これに対してコンポーネントごとのスタイルをローカルに割り当てるのがCSSモジュールです。

では、CSSモジュールを新たなコンポーネントモジュールcomponents/layout.tsxで使ってみましょう。このコンポーネントは、あとで他のすべてのページで共有されます。まだ、CSSモジュールは定めていません。

components/layout.tsx
import type { FC, ReactNode } from 'react';

type Props = {
	children: ReactNode;
};
const Layout: FC<Props> = ({ children }) => {
	return <div>{children}</div>;
};
export default Layout;

まず、pages/posts/first-post.tsxLayoutコンポーネントを読み込み、JSXのフラグメント<>と置き替えます。つまり、コンポーネントFirstPostLayoutで包み込むのです。

pages/posts/first-post.tsx
import Layout from '../../components/layout';

const FirstPost: FC = () => {
	return (
		// <>
		<Layout>

		{/* </> */}
		</Layout>
	);
};

CSSモジュールcomponents/layout.module.cssは、以下のように定めます。CSSの書き方にとくに変わったところはありません。ただし、ファイル名は.module.cssで結んでください。

Important: To use CSS Modules, the CSS file name must end with .module.css.
(「Adding CSS」)

components/layout.module.css
.container {
	max-width: 36rem;
	padding: 0 1rem;
	margin: 3rem auto 6rem;
}

CSSモジュールはcomponents/layout.tsxから以下のように用います。

  • importしたCSSモジュールファイルのスタイルに名前(ここではstyles)を与えてください。
  • スタイルから参照したクラス(ここではstyles.container)をclassNameに加えます。
components/layout.tsx
import styles from './layout.module.css';

const Layout: FC<Props> = ({ children }) => {
	// return <div>{children}</div>;
	return <div className={styles.container}>{children}</div>;
};

pages/posts/first-post.tsxのページには、CSSモジュールのスタイルが割り当てられたでしょう。そのうえで、デベロッパーツールでclassを確かめてください。すると、モジュールとクラスの名前を示す識別子のあとに、つぎのような一意の記号(__fbLkO)が加わったはずです。一意の記号が自動的に生成されるために、CSSモジュールではクラス名の重複は起こらないのです。

<div class="layout_container__fbLkO">...</div>

グローバルスタイルを定める

すでにひな形プロジェクトにつくられていたstyles/globals.cssは、以下のようにpages/_app.tsxに読み込まれて、アプリケーションのすべてのページに用いられます。ただし、グローバルCSS(globals.css)をimportできるのはpages/_app.tsxだけです。

In Next.js, you can add global CSS files by importing them from pages/_app.js. You cannot import global CSS anywhere else.
(「Adding Global CSS」)

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

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

pages/_app.tsxexport defaultされたReactコンポーネント(App)は、トップレベルでアプリケーションのすべてのページを包みます。状態はページを遷移しても保たれ、グローバルスタイルもこのコンポーネントに加えるのです。pages/_app.tsxを書き替えたときは、開発サーバーを[control] + [C]で停止したうえで、再起動(npm run dev)してください。

なお、プロジェクトのstyles/globals.cssの定めは、GitHubに公開された公式作例のコードで上書きました。

レイアウトを改善する

CSSモジュールのスタイルにもう少し手を加えましょう。components/layout.module.cssにつぎのクラスを加えてください。

components/layout.module.css

.header {
	display: flex;
	flex-direction: column;
	align-items: center;
}
.backToHome {
	margin: 3rem 0 0;
}

さらに、CSSモジュールstyles/utils.module.cssを、GitHubのコードから新たにコピーして加えます。定められているのは複数のコンポーネントから使いまわされるユーティリティ的なクラスです。

そのうえで、モジュールcomponents/layout.tsxにつぎのようにCSSモジュールstyles/utils.module.cssimportし、コンテンツも書き加えます。コンポーネントが引数に受け取るプロパティhomeは、トップページを他と区別してレイアウトするためのフラグです(JSXの中で条件分岐させていることをお確かめください)。なお、変数nameの値の文字列('Your Name')は書き替えて構いません。

components/layout.tsx
import Head from 'next/head';
import Image from 'next/image';
import Link from 'next/link';

import utilStyles from '../styles/utils.module.css';

const name = 'Your Name';
export const siteTitle = 'Next.js Sample Website';
type Props = {

	home?: boolean;
};
// const Layout: FC<Props> = ({ children }) => {
const Layout: FC<Props> = ({ children, home }) => {
	// return <div className={styles.container}>{children}</div>;
	return (
		<div className={styles.container}>
			<Head>
				<link rel="icon" href="/favicon.ico" />
				<meta
					name="description"
					content="Learn how to build a personal website using Next.js"
				/>
				<meta
					property="og:image"
					content={`https://og-image.vercel.app/${encodeURI(
						siteTitle,
					)}.png?theme=light&md=0&fontSize=75px&images=https%3A%2F%2Fassets.vercel.com%2Fimage%2Fupload%2Ffront%2Fassets%2Fdesign%2Fnextjs-black-logo.svg`}
				/>
				<meta name="og:title" content={siteTitle} />
				<meta name="twitter:card" content="summary_large_image" />
			</Head>
			<header className={styles.header}>
				{home ? (
					<>
						<Image
							priority
							src="/images/profile.png"
							className={utilStyles.borderCircle}
							height={144}
							width={144}
							alt=""
						/>
						<h1 className={utilStyles.heading2Xl}>{name}</h1>
					</>
				) : (
					<>
						<Link href="/">
							<Image
								priority
								src="/images/profile.png"
								className={utilStyles.borderCircle}
								height={108}
								width={108}
								alt=""
							/>
						</Link>
						<h2 className={utilStyles.headingLg}>
							<Link href="/" className={utilStyles.colorInherit}>
								{name}
							</Link>
						</h2>
					</>
				)}
			</header>
			<main>{children}</main>
			{!home && (
				<div className={styles.backToHome}>
					<Link href="/">← Back to home</Link>
				</div>
			)}
		</div>
	);
};

そして、トップページのpages/index.tsxモジュールに手を加えたのがつぎのコードです。Layoutコンポーネントで包みましたので、<Image>は要らなくなりましたし、アイコンの定めも除きました。[Your Self Introduction]は自己紹介文に書き替えてください。トップページのレイアウトが変わりました。

pages/index.tsx
import Head from 'next/head';
// import Link from 'next/link';
import Layout, { siteTitle } from '../components/layout';
import utilStyles from '../styles/utils.module.css';

export default function Home() {
	return (
		/* <>

		</> */
		<Layout home>
			<Head>
				<title>{siteTitle}</title>
			</Head>
			<section className={utilStyles.headingMd}>
				<p>[Your Self Introduction]</p>
				<p>
					(This is a sample website - you’ll be building a site like this on{' '}
					<a href="https://nextjs.org/learn">our Next.js tutorial</a>.)
				</p>
			</section>
		</Layout>
	);
}

図002■改められたトップページのレイアウト

qiita_2301001_008.png

モジュールpages/posts/first-post.tsxは、このあとはもう使いません。けれど、つぎのように少し手を入れてトップページと比べてみましょう。やはり、Layoutコンポーネントで包みました。コンポーネントFirstPosthomeプロパティを受け取らないので、トップページではないという扱いです。すると、プロフィール画像がトップページより小さくなるとともに、「← Back to home」のリンクが表示されます。この条件判定は、Layoutコンポーネントが行っているのです。ここまでのコードは、以下のソース01に公開しました。

pages/posts/first-post.tsx
// import Image from 'next/image';
// import Link from 'next/link';

import Layout from '../../components/layout';

const FirstPost: FC = () => {
	return (
		// <>
		<Layout>
			<Head>
				<title>First Post</title>
			</Head>
			{/* <Image

			/> */}

			{/* <h2>
				<Link href="/">← Back to home</Link>
			</h2> */}
		{/* </> */}
		</Layout>
	);
};

ソース01■Next入門02 イメージとメタデータおよびCSSを扱う

トップページのレイアウトが少し公式作例に近づきました。次回は、データを動的に取得して、トップページに表示します。

React + TypeScript: Next入門シリーズ

5
3
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
5
3