はじめに
実際にあなたのポートフォリオページを閲覧するユーザーのトップページをnextjsで作成していきます。
next.jsではrailsの参照しているデータベースに直接アクセスはできないようにしたいので、next.jsがrailsに対してリクエストを送信して、railsはそれを受けてデータベースを参照してそのリソースをレスポンスする。next.jsが受け取ったレスポンスを元に画面を表示させる
という流れになります。
next.jsがrailsに対してリクエストを送信してレスポンスを受け取るところはそれなりにボリュームがあるので、ここではシンプルにダミーで見た目だけそれっぽく写っていればよしとします。
ここで言い訳をしておきますが、私はバックエンドの開発経験の方が多くフロントエンドの開発経験は少なめです。特にhtml/cssのスタイリングは学習は一通りしましたが、ガッツリ業務で組んだりはしていません。ちょくちょく非効率なところもあるかと思います。見苦しい箇所もあるかと思いますが、ご容赦ください。
ご指摘いただければ改善したいとおもいます!
ゴールの確認
scssをインストールする
/のページにアクセスしたときにトップページがダミーで表示されるようにする
nextjsのディレクトリ構造を構築していく
トップページの画面編集
まずは現在のnext.jsのデフォルトのトップページからカスタマイズしていきます。
next.jsのトップページはapp/page.tsxにあります。これを編集します。
export default function TopPage() {
return <div>this is my profile top page</div>;
}
これでトップページが変わりました。
Sassのインストール
cssを使って自由にスタイルをカスタマイズしていくのですが、cssのみだと機能が少なくて不自由です。
サイトの中で統一したスタイリングを用いるために変数定義などの機能が欲しいのでscssを採用します。
scssのインストールの方法はnext.jsの公式ページに記載があります。
dockerを使っているので、ちょっとコマンドに工夫が必要です。
docker compose run -w /app --rm frontend npm install --save-dev sass
これでscssを使う準備ができました。
次にglobal.cssをglobal.scssに変更します。
layout.tsxにてglobal.cssとファイル名を指定してimportしている箇所があるのでこれをscssに修正します。
基本のスタイル
global.scssの中身を変えていきます。
@use "/src/app/variables.scss" as *;
@tailwind base;
@tailwind components;
@tailwind utilities;
:root {
--foreground-rgb: 0, 0, 0;
--background-start-rgb: 214, 219, 220;
--background-end-rgb: 255, 255, 255;
}
@media (prefers-color-scheme: dark) {
:root {
--foreground-rgb: 255, 255, 255;
--background-start-rgb: 0, 0, 0;
--background-end-rgb: 0, 0, 0;
}
}
@layer utilities {
.text-balance {
text-wrap: balance;
}
}
* {
padding: 0;
margin: 0;
box-sizing: border-box;
}
html {
font-size: 62.5%;
}
body {
color: rgb(var(--foreground-rgb));
background-color: $color-white;
}
body {
font-family: "Rubik", sans-serif;
font-weight: 400;
color: $color-font;
}
次に変数を置くためのファイルを作成します
// color
$color-white: #fff;
$color-font: #555;
$color-main: #339af0;
// font
$fontsize-lg: 4rem;
$fontsize-md: 2rem;
$fontsize-sm: 1.2rem;
// space
$space-md: 10px;
コンポーネントの作成
ベースのスタイリングができたので、ここから本格的に見た目を作成していきます。
下記のような見た目ページを目指します。(こちらはFigmaを使って作成しました。事前にどんなページになるか作っておくと実装時にサクサク作りやすいです。)
コンポーネントの設計の方針としては、まず、アプリ全体で使いそうなコンポーネントはfrontend/src/componentsの中におき、ある機能(例: careers)でのみ使うであろうコンポーネントはfrontend/src/features/careers/componentsの中においていきます。
また、page.tsxファイルは一つのテンプレートコンポーネントを置くだけの方針にしたいと思います。
実装開始
それではコンポーネントを作成していきましょう。
この時点で用意されていないディレクトリがある場合は、一緒にそのディレクトリも作成するようにしてください
メールアイコンのコンポーネントを用意
export default function MailIcon() {
return (
<svg
xmlns="http://www.w3.org/2000/svg"
fill="currentColor"
className="bi bi-envelope"
viewBox="0 0 16 16"
>
<path d="M0 4a2 2 0 0 1 2-2h12a2 2 0 0 1 2 2v8a2 2 0 0 1-2 2H2a2 2 0 0 1-2-2V4Zm2-1a1 1 0 0 0-1 1v.217l7 4.2 7-4.2V4a1 1 0 0 0-1-1H2Zm13 2.383-4.708 2.825L15 11.105V5.383Zm-.034 6.876-5.64-3.471L8 9.583l-1.326-.795-5.64 3.47A1 1 0 0 0 2 13h12a1 1 0 0 0 .966-.741ZM1 11.105l4.708-2.897L1 5.383v5.722Z" />
</svg>
);
}
ヘッダー部分のスタイル
@use "/src/app/variables.scss" as *;
.header {
height: 30rem;
background-color: $color-main;
position: relative;
.container {
padding-left: 32rem;
color: $color-white;
.userName {
padding-top: 6rem;
font-size: 10rem;
line-height: 9rem;
}
.email {
display: flex;
font-size: $fontsize-md;
margin-left: 1rem;
align-items: center;
.icon {
margin-right: $space-md;
width: 2.5rem;
}
}
.navBar {
display: flex;
position: absolute;
bottom: 0;
padding: 1rem;
.navItem {
font-size: $fontsize-md;
margin-right: 5rem;
}
.active {
border-bottom: 5px solid;
}
}
}
}
ヘッダーのコンポーネント
import styles from "@/components/header/header.module.scss";
import MailIcon from "../icons/mail";
export default function Header() {
return (
<header className={styles.header}>
<div className={styles.container}>
<div className={styles.profile}>
<div className={styles.userName}>User Name</div>
<div className={styles.email}>
<div className={styles.icon}>
<MailIcon />
</div>
<div>user-mail@mail.com</div>
</div>
</div>
<ul className={styles.navBar}>
<li className={`${styles.navItem} ${styles.active}`}>career</li>
<li className={styles.navItem}>portfolios</li>
<li className={styles.navItem}>blogs</li>
</ul>
</div>
</header>
);
}
キャリア一覧のスタイル
@use "/src/app/variables.scss" as *;
.container {
margin-left: 32rem;
padding-top: 4rem;
width: 90rem;
border-bottom: solid 1px $color-main;
.item {
border-top: solid 1px $color-main;
display: flex;
margin-bottom: 5rem;
.icon {
position: relative;
left: -2rem;
content: "";
width: 7.5rem;
height: 5rem;
background-color: $color-main;
border-radius: 50%;
margin-top: 10px;
}
.icon::after {
content: "";
display: block;
width: 1px;
height: 100%;
background-color: $color-main;
position: absolute;
left: 0;
right: 0;
top: 60px;
margin: auto;
}
.content {
.title {
font-size: $fontsize-lg;
align-items: center;
margin-top: $space-md;
}
.description {
font-size: $fontsize-sm;
}
}
}
}
キャリア一覧のコンポーネント
import styles from "@/features/careers/components/career-list.module.scss";
export default function CareerList() {
return (
<div className={styles.container}>
<div className={styles.item}>
<div className={styles.icon}></div>
<div className={styles.content}>
<div className={styles.title}>Career 1</div>
<div className={styles.description}>
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do
eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim
ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut
aliquip ex ea commodo consequat.{" "}
</div>
</div>
</div>
<div className={styles.item}>
<div className={styles.icon}></div>
<div className={styles.content}>
<div className={styles.title}>Career 2</div>
<div className={styles.description}>
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do
eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim
ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut
aliquip ex ea commodo consequat.{" "}
</div>
</div>
</div>
<div className={styles.item}>
<div className={styles.icon}></div>
<div className={styles.content}>
<div className={styles.title}>Career 3</div>
<div className={styles.description}>
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do
eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim
ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut
aliquip ex ea commodo consequat.{" "}
</div>
</div>
</div>
</div>
);
}
キャリアトップのテンプレートコンポーネント
import Header from "@/components/header/header";
import CareerList from "@/features/careers/components/career-list";
export default function CareerTopTemplate() {
return (
<div>
<Header />
<CareerList />
</div>
);
}
キャリアトップのテンプレートをTopPageで読み込み
import CareerTopTemplate from "@/features/careers/components/career-top-template";
export default function TopPage() {
return <CareerTopTemplate />;
}
おつかれ様でした。
うまく設定できていれば下記のような見た目になっていると思います。
おわりに
コンポーネントの作成の手順にて、最初に小さいコンポーネントを作成してから段々と大きい枠のコンポーネントを作成するような順序で紹介しましたが、出来上がったソースコードをもとに紹介するとこの手順が自然だろうと思いこの順序にしましたが、実際の実装の手順は逆です。
まず、page.tsxにhtmlを入れて、それが反映されることを確認したら、templateのコンポーネントを作成してそれをpage.tsxに読み込ませてtemplateのコンポーネントに適当なhtmlを入れてそれが反映されるかを確認。そこからheaderをそのままhtml/scssを入れてある程度できたところでheaderコンポーネントを作成してそっちに移動….のような感じで作成しています。
回りくどいですが、小さく確認することでもし予想外の結果が起きたときに何が問題かを特定しやすくするための手順です。next.jsでの開発するも何度目かですが、それでも一つ一つ確認していっています。初めのうちはこれでもかというくらいスモールステップを踏んでいくのが私の思う成功の鍵です!
現在、名前、メールアドレス、キャリア項目はダミーで決め打ちの文言を入れていますが、おいおいrailsサーバーからAPI経由で動的な値になるようにしていきます。
コメント