Next.jsチュートリアルの公式作例を、TypeScriptも加えて、一からつくってみようというシリーズの第2回です。前回は、トップページを書き替えて、新たに追加した別ページとリンクしてみたというくらいでした。今回から、公式作例に少しずつ近づけてゆきましょう。
Image
コンポーネントでイメージを差し込む
Next.jsはイメージなどの静的素材も扱えます。素材ファイルを置くトップレベルは、public
ディレクトリです。Reactコンポーネントを収めるpages
と同じく、アプリケーションのルートから参照できます。
ページに表示するプロフィール画像を用意しましょう。ディレクトリpublic
にimages
をつくってください。
- 画像を
public/images/profile.png
として収めます。-
next-learn/basics/basics-final/public/images/profile.jpg
を使っても構いません(拡張子が.jpg
であることにご注意ください)。
-
- イメージの大きさは幅400px×高さ400pxくらいが適切です。
- 作例のコードは正方形を前提としています。
- ひな形プロジェクトにつくられた
public
ディレクトリの使わないSVGファイルは削除して構いません。-
public/favicon.ico
は残しておいてください。
-
<Image>のImage
コンポーネントは、イメージの扱いを最適化するために備わりました。Image
コンポーネントの使い方はつぎに示したコード例のとおりです(図001)。width
とheight
には希望のサイズが与えられます。アスペクト比は元画像の比率を保ってください。
import Image from 'next/image';
const FirstPost: FC = () => {
return (
<>
<Image
src="/images/profile.png" // イメージファイルのルート
height={144} // イメージサイズの設定
width={144} // アスペクト比を正しく
alt="Your Name"
/>
</>
);
};
図001■ページにLink
コンポーネントでプロフィール画像が加わった
Head
コンポーネントでメタデータを定める
<title>
など<head>
要素に加えるページのメタデータをサポートするのがHead
コンポーネント(<Head>)です。モジュールpages/index.tsx
にHead
コンポーネントをつぎのように加えてみましょう。トップページに<title>
とアイコンが表示されたはずです。
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>
をつぎのように定めます。ページを遷移すると、ブラウザのタブに示されるタイトルが切り替わるようになりました。
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モジュールは定めていません。
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.tsx
でLayout
コンポーネントを読み込み、JSXのフラグメント<>
と置き替えます。つまり、コンポーネントFirstPost
をLayout
で包み込むのです。
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」)
.container {
max-width: 36rem;
padding: 0 1rem;
margin: 3rem auto 6rem;
}
CSSモジュールはcomponents/layout.tsx
から以下のように用います。
-
import
したCSSモジュールファイルのスタイルに名前(ここではstyles
)を与えてください。 - スタイルから参照したクラス(ここでは
styles.container
)をclassName
に加えます。
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」)
import '@/styles/globals.css'
import type { AppProps } from 'next/app'
export default function App({ Component, pageProps }: AppProps) {
return <Component {...pageProps} />
}
pages/_app.tsx
のexport default
されたReactコンポーネント(App
)は、トップレベルでアプリケーションのすべてのページを包みます。状態はページを遷移しても保たれ、グローバルスタイルもこのコンポーネントに加えるのです。pages/_app.tsx
を書き替えたときは、開発サーバーを[control] + [C]で停止したうえで、再起動(npm run dev
)してください。
なお、プロジェクトのstyles/globals.css
の定めは、GitHubに公開された公式作例のコードで上書きました。
レイアウトを改善する
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.css
をimport
し、コンテンツも書き加えます。コンポーネントが引数に受け取るプロパティhome
は、トップページを他と区別してレイアウトするためのフラグです(JSXの中で条件分岐させていることをお確かめください)。なお、変数name
の値の文字列('Your Name'
)は書き替えて構いません。
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]
は自己紹介文に書き替えてください。トップページのレイアウトが変わりました。
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■改められたトップページのレイアウト
モジュールpages/posts/first-post.tsx
は、このあとはもう使いません。けれど、つぎのように少し手を入れてトップページと比べてみましょう。やはり、Layout
コンポーネントで包みました。コンポーネントFirstPost
はhome
プロパティを受け取らないので、トップページではないという扱いです。すると、プロフィール画像がトップページより小さくなるとともに、「← Back to home」のリンクが表示されます。この条件判定は、Layout
コンポーネントが行っているのです。ここまでのコードは、以下のソース01に公開しました。
// 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入門シリーズ
- Next入門01 チュートリアルの作例を一からつくってみる
- Next入門02 イメージとメタデータおよびCSSを扱う
- Next入門03 プリレンダリングとデータ取得
- Next入門04 動的ルートを定める