Next.js/TypeScriptでportfolio作ってみた

# はじめに
今回Next.js/TypeScriptを使用してportfolioを作成したので、作り方を共有しようと思います。これからportfolioを作りたい方の参考になれば幸いです。

こちらが完成品です。

https://t-natsuki-portfolio.vercel.app/

https://github.com/natsukikokubu/portfolio

# 全体の流れ
- portfolioの構成を考える
- 環境構築をする
- 黙々とコードを書く
- デプロイする

# 作り方
## 1.portfolioの構成を考える
簡単にではありますが、Figmaで作成しました。

![スクリーンショット 2024-08-15 19.37.46.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3582310/ab01a3ef-9a1e-8496-9d31-a1e680070cfd.png)

## 2.環境構築
- GitHubでリポジトリを作成してください
- Next.jsをインストールします
  - ターミナルで以下のコマンドを入力してください
`npx create-next-app@latest`
```
 Need to install the following packages:
    create-next-app@14.2.4
    Ok to proceed? (y) y
    ✔ What is your project named? … portforlio
    ✔ Would you like to use TypeScript? … No / Yes # Yesを選択
    ✔ Would you like to use ESLint? … No / Yes　 # Yesを選択
    ✔ Would you like to use Tailwind CSS? … No / Yes　 # Yesを選択
    ✔ Would you like to use `src/` directory? … No / Yes　 # Yesを選択
    ✔ Would you like to use App Router? (recommended) … No / Yes　 # Noを選択
    ✔ Would you like to customize the default import alias (@/*)? … No / Yes # Yesを選択
    ✔ What import alias would you like configured? … @/*
```

## 3.コードを書く
### componentsを作成

**header.tsx**
スムーススクロールを実装するにあたり、react-scrollというライブラリがあったので使用しました。**react-scroll**というのは、スクロール機能を実装するためのライブラリで、ページ内の特定のセクションや要素にスクロールのためのリンクを作成できます。
Linkコンポーネントのtoプロパティにスクロール先の要素名を指定してスクロール先にはidで名前を付けておきます。
`npm install react-scroll`これでinstallできます。

```tsx
import { Link as ScrollLink } from "react-scroll";

export const Header = () => {
  return (
    <header className="flex justify-between px-4 sm:px-24 h-16 w-screen bg-white bg-opacity-80 sticky top-0 z-50">
      <h1 className="text-xl font-bold text-gray-700 flex items-center sm:text-2xl ">
        portfolio
      </h1>
      <nav>
        <ul className="flex gap-8 sm:gap-10 items-center h-full">
          <li>
            <ScrollLink to="top" smooth duration={300}>
              Top
            </ScrollLink>
          </li>
          <li>
            <ScrollLink to="works" smooth duration={300}>
              Works
            </ScrollLink>
          </li>
          <li>
            <ScrollLink to="contact" smooth duration={300}>
              Contact
            </ScrollLink>
          </li>
        </ul>
      </nav>
    </header>
  );
};

```

**workcard.tsx**
```tsx
import React from "react";

type WorkCardProps = {
  title: string;
  description: string;
  img: string;
  link: string;
};

export const WorkCard: React.FC<WorkCardProps> = ({
  title,
  description,
  img,
  link,
}) => {
  return (
    <div className="bg-white bg-opacity-5 rounded-xl h-60 w-100 p-4 z-30 ">
      <h2 className="m-auto">{title}</h2>
      <p className="mb-2 m-auto">{description}</p>
      <a target="_blank" href={link}>
        <img
          className="rounded-xl w-60 h-36 m-auto hover:opacity-80"
          src={img}
        />
      </a>
    </div>
  );
};
```
**components/index.ts**

```ts
export { Header } from "./header";
export { WorkCard } from "./workcard";
```
:::note info
 index.tsにて同一ディレクトリのモジュールを再エクスポートをすることで、利用者側で一度に複数のモジュールをimportすることができて管理がしやすくなります。また、どのモジュールがエクスポートされているのかが一覧できて見通しが良くなります。
:::

### メインのページを作成
**pages/index.tsx**
```tsx
import React from "react";
import Image from "next/image";
import { Header, WorkCard } from "@/components";

export default function Home() {
  return (
    <main className="font-body bg-gray-900 z-50">
      <Header />
      <section className="flex flex-col items-center  py-16 h-140 bg-gray-900 text-blue-100">
        <h1 className="text-lg font-bold my-4" id="top">
          About me
        </h1>
        <Image
          src="/me.png"
          alt="me"
          width="160"
          height="160"
          className="z-30"
        />
        <p className="pt-2 pb-4">Tachibana Natsuki</p>
        <div className="flex flex-col m-4  sm:ml-4">
          <div className="ml-4 mt-2">
            <dt className="inline whitespace-nowrap">生年月日 :</dt>
            <dd className="inline ml-3 break-all">1998年11月13日(25歳)</dd>
          </div>
          <div className="ml-4 mt-2">
            <dt className="inline whitespace-nowrap">趣味 :</dt>
            <dd className="inline ml-3 break-all">読書、ヨガ、勉強</dd>
          </div>
          <div className="ml-4 mt-2">
            <dt className="inline whitespace-nowrap">好きな動物 :</dt>
            <dd className="inline ml-3 break-all">うさぎ</dd>
          </div>
          <div className="ml-4 mt-2">
            <dt className="inline whitespace-nowrap">経歴 :</dt>
            <dd className="inline ml-3 break-all">
             総合病院
              <br />
             会社員
            </dd>
          </div>
          <div className="ml-4 mt-2">
            <dt className="inline whitespace-nowrap">ITスキル :</dt>
            <dd className="inline ml-3 break-all">
              HTML,CSS,Javascript,Typescript,React,Next.js,Github
            </dd>
          </div>
          <div className="ml-4 mt-2">
            <dt className="inline whitespace-nowrap">ひとこと :</dt>
            <dd className="inline ml-3 break-all">
              新しいことを学ぶのが好きで、働きながらプログラミングをしてました。
              <br />
              ITスキルをつけて世の中に貢献していきたいです。
            </dd>
          </div>
        </div>
      </section>
      <section className="relative flex flex-col items-center bg-gray-900 text-blue-100">
        <h1 className=" text-lg font-bold my-8 mb-8" id="works">
          Works
        </h1>
        <ul className=" grid grid-cols-1 md:grid-cols-2 gap-x-24 gap-y-8 z-30">
          <li>
            <WorkCard
              title="日記アプリ"
              description="日記を書いて保存できるアプリです。"
              img="/diaryApp.png"
              link="自分の作品のリンクを入れてください"
            />
          </li>
          <li>
            <WorkCard
              title="読書記録アプリ"
              description="読書記録を保存できるアプリです。"
              img="/bookApp.png"
              link="自分の作品のリンクを入れてください"
            />
          </li>
        </ul>
      </section>
      <section className="relative flex flex-col items-center h-80  bg-gray-900 text-blue-100">
        <h1 className="text-lg font-bold my-16  mb-12" id="contact">
          Contact
        </h1>
        <div className="flex flex-right items-center justify-between gap-16 z-30">
          <a
            href="https://x.com/na_u0801"
            target="_blank"
            rel="noopener noreferrer"
          >
            <img className="object-contain w-8" src="/logo-white.png" />
          </a>
          <a
            href="https://github.com/natsukikokubu"
            target="_blank"
            rel="noopener noreferrer"
          >
            <img className="object-contain w-8" src="/github-mark-white.png" />
          </a>
          <a
            href="https://www.wantedly.com/id/kokubu_natsuki"
            target="_blank"
            rel="noopener noreferrer"
          >
            <img className="object-contain w-12" src="/Wantedly_Mark_Wht.png" />
          </a>
        </div>
      </section>
    </main>
  );
}

```
背景ですが、particles.jsを使用してみました。自分好みの幾何学模様が作成できます。

https://vincentgarreau.com/particles.js/

組み込みは以下のサイトを参考にさせていただきました。

https://note.com/webnote32/n/n56cd57610765

- particlesjs-config.jsonをダウンロードします
`npm install react-tsparticles
npm install tsparticles-slim`

自身でダウンロードしたparticlesjs-config.jsonをsrc/assets/配下に配置します。
そして以下のようにファイルをインポートしてカスタムデザインを適用します。

**_app.tsx**
```tsx
import "../styles/globals.css";
import type { AppProps } from "next/app";
import { ComponentProps, useCallback } from "react";
import { Container, Engine } from "tsparticles-engine";
import Particles from "react-tsparticles";
import { loadSlim } from "tsparticles-slim";
import template from "@/assets/particlesjs-config.json";

export default function App({ Component, pageProps }: AppProps) {
  const params = template as ComponentProps<typeof Particles>["options"];
  const particlesInit = useCallback(async (engine: Engine) => {
    await loadSlim(engine);
  }, []);

  const particlesLoaded = useCallback(
    async (container: Container | undefined) => {
      await console.log(container);
    },
    []
  );

  return (
    <>
      <Component {...pageProps} />
      <Particles
        id="tsparticles"
        init={particlesInit}
        loaded={particlesLoaded}
        options={params}
      />
    </>
  );
}
```
:::note warn
配置したworkcardにはz-indexを指定しておかないと背景が前に出てしまいクリックができなくなってしまいますので注意してください。
:::

## 4.デプロイする
vercelを使用しました。
- AddNewボタンで新しくプロジェクトを作成する
- ポートフォリオのファイルをimportする
- Deployする

# 最後に
ここまで読んで下さりありがとうございました！
至らない点は沢山あるかと思いますので、ご指摘いただけますと幸いです。