2025-1-13

Reactでポートフォリオサイトを作成する 🚀(2)

ReactNext.jsポートフォリオ

はじめに

「自分にとって新しい技術をインプットしたい」と「長く残るアウトプットを作りたい」という思いから、Reactを使ってポートフォリオサイトの作成に挑戦してみることにしました。 この記事を含め、複数回に分けて作成過程を記録していきます。ここまでの記事は以下です。
今回は以下の内容についてご紹介します。
  • 【ヘッダー】TOP画面とそれ以外の画面で表示を分ける
  • 【プロフィール画面】プロフィールを記載する箱を作成
  • 【プロフィール画面】ページタイトルなどをコンポーネント化

【ヘッダー】TOP画面とそれ以外の画面で表示を分ける

以下記事を参考に、ホーム画面以外の時はホーム画面へのリンクを左側に表示させるようにする
usePathnameを利用する時は`"use client"を追加してクライアントコンポーネントにする必要がある
Header.tsx
'use client'

import Link from 'next/link'
import { usePathname } from 'next/navigation'

const Header = () => {
  const pathname = usePathname()
  const isMainPage = pathname === '/'

  return (
    <header className="pt-5 pl-5 mb-20">
      <div className="container mx-auto flex flex-wrap p-5 flex-col md:flex-row items-center">
        {!isMainPage && (
          <a
            href="/"
            className="flex font-mobo font-medium items-center mb-4 md:mb-0"
          >
            <span className="ml-3 text-xl">Motoshi Furugen</span>
          </a>
        )}
        <nav className="md:ml-auto flex flex-wrap items-center text-base justify-center font-mobo">
          <Link className="mr-10 hover:opacity-50" href="/profile">
            プロフィール
          </Link>
          <Link className="mr-10 hover:opacity-50" href="#">
            開発ブログ
          </Link>
          <Link className="mr-10 hover:opacity-50" href="#">
            実績
          </Link>
          <Link className="mr-10 hover:opacity-50" href="#">
            コンタクト
          </Link>
          <Link className="mr-10 hover:opacity-50" href="#">
            ソースコード
          </Link>
        </nav>
      </div>
    </header>
  )
}

export default Header
ヘッダーを上部に固定させる。headerに"fixed"を追加して、main要素にpadding-topを追加する。
Header.txs
<header className="fixed top-0 left-0 w-full z-50 py-5 pl-5 mb-20 bg-bg-main">
layout.tsx
<html lang="en">
  <body className={inter.className}>
    <Header />
    <main className="pt-40">{children}</main>
  </body>
</html>
Githubへのリンクを設置する
Header.tsx
<Link
  className="mr-10 hover:bg-gray"
  href="https://github.com/motoshifurugen/my_site"
  target="_blank"
  rel="noopener noreferrer"
>
  <div className="flex items-center border border-gray-300 rounded px-3 py-1">
    <FontAwesomeIcon icon={faGithub} className="mr-2" />
    ソースコード
  </div>
</Link>
screenshot

【プロフィール画面】画面作成

MainMessegeコンポーネント内に入れていたプロフィールへのリンク部分はpage.tsxに一旦移し、メインメッセージをプロフィール画面でも使うことにする。
/pofile/page.tsx
import MainMessage from '../components/MainMessage'

export default function Page() {
  return (
    <section className="profile px-20">
      <div className="flex justify-center h-screen">
        <MainMessage />
      </div>
    </section>
  )
}
プロフィール欄を用意して、左に説明・右に写真を配置する。
/pofile/page.tsx
<section className="profile px-20">
  <h1 className="text-4xl font-bold">プロフィール</h1>
  <div className="flex justify-center">
    <MainMessage />
  </div>
  <div>
    <h2 className="text-3xl font-bold">経歴</h2>
    <div className="flex p-10">
      <p className="flex w-1/2 text-lg leading-loose items-center">
        <p className="mr-6 text-right">
          1998          <br />
          2017          <br />
          2020          <br />
          2021          <br />
          2023          <br />
          現在
        </p>
        <p>
          沖縄に生まれる
          <br />
          高校卒業後、広島大学理学部物理学科へ進学する
          <br />
          大学休学中にプログラミングを始める
          <br />
          長期インターンでWebエンジニアを経験する
          <br />
          大学卒業後、エンジニアとして就職する
          <br />
          フロントエンドエンジニアとして奮闘中
        </p>
      </p>
      <div className="w-1/2 flex justify-end">
        <Image
          src="/images/etsushi.jpg"
          alt="profile img 01"
          width={500}
          height={500}
        />
      </div>
    </div>
  </div>
</section>
screenshot
プロフィールで使用した3つの紹介(経歴・興味・趣味)の記述部分をコンポーネント化する。
Card.tsx
// components/Card.tsx
import { ReactNode } from "react";
import Image from "next/image";

interface CardProps {
  title: string;
  content: ReactNode;
  imageSrc: string;
  imageAlt: string;
}

const Card: React.FC<CardProps> = ({ title, content, imageSrc, imageAlt }) => {
  return (
    <div className="card">
      <h2 className="text-3xl font-bold">{title}</h2>
      <div className="flex p-10 font-open-sans">
        <div className="flex w-1/2 text-lg leading-loose items-center">
          {content}
        </div>
        <div className="w-1/2 flex justify-end">
          <Image src={imageSrc} alt={imageAlt} width={500} height={500} />
        </div>
      </div>
    </div>
  );
};

export default Card;
profile/page.tsxにて、Cardコンポーネントを使用する。
profile/page.tsx
<Card
  title="経歴"
  content={
    <>
      <p className="mr-6 text-right">
        1998<br />
        2017<br />
        2020<br />
        2021<br />
        2023<br />
        現在
      </p>
      <p>
        沖縄に生まれる<br />
        高校卒業後、広島大学理学部物理学科へ進学する<br />
        大学休学中にプログラミングを始める<br />
        長期インターンでWebエンジニアを経験する<br />
        大学卒業後、エンジニアとして就職する<br />
        フロントエンドエンジニアとして奮闘中
      </p>
    </>
  }
  imageSrc="/images/profile_01.jpg"
  imageAlt="profile img 01"
/>
<Card
  title="興味"
  content={
    <>
       <p>
         物理学が目に見えない自然の法則を解き明かすように、<br />
         データという見えない情報を扱うことに楽しさを感じています。<br />
         最近はバックエンドやネットワーク分野に興味があり、<br />
         今年はネットワークスペシャリストの資格に挑戦します。<br />
         心が強い方ではないので、メンタルヘルスへの関心も大切にしています。
       </p>
     </>
   }
   imageSrc="/images/profile_02.png"
   imageAlt="profile img 02"
 />
 <Card
   title="趣味"
   content={
     <>
       <p>
         エイサー(沖縄の伝統芸能) ・ 読書(ビジネス書中心) ・<br />
         散歩 ・ 短歌 ・ ギター(アコギ) ・ ドライブ ・ ボウリング <br />
         and more<br />
       </p>
     </>
  }
  imageSrc="/images/profile_03.jpg"
  imageAlt="profile img 03"
/>

【プロフィール画面】ページタイトルなどをコンポーネント化

画面説明部分をコンポーネントに切り分ける
PageFace.tsx
import React from 'react';

interface PageFaceProps {
  title: string;
  subtitle: string;
  mainMessage: React.ReactNode;
}

const PageFace: React.FC<PageFaceProps> = ({ title, subtitle, mainMessage }) => {
  return (
    <div className="flex mb-24">
      <div className="w-1/2">
        <h1 className="text-4xl font-bold">{title}</h1>
        <h2 className="text-2xl font-bold mt-5">{subtitle}</h2>
      </div>
      <div className="flex justify-left w-1/2">
        {mainMessage}
      </div>
    </div>
  );
};

export default PageFace;
/profile/page.tsx
<PageFace
  title="プロフィール"
  subtitle="古堅基史(Furugen Motoshi)"
  mainMessage={<MainMessage />}
/>
PageFaceコンポーネントの下部に直線を表示させる。(アニメーション付き)
PageFace.tsx
<div
  ref={lineRef}
  className="h-0.5 opacity-50 bg-font-main transition-all duration-1000 ease-in-out w-0 mt-4 mb-24"
></div>
screenshot

to be continued...

次回はスキル・実績画面の作成を行います。