【NextJS】SassとTypescriptでレイアウトを構成

1. 概要

上記、記事ではNext.jsやApollo Clientを使い、GraphQLよりデータを取得する内容でした。今回からはNext.jsやReactの基礎内容となります。

対象としては開発を1年程やってて自分で最初から開発してみたい方になります。そのため細かい用語などの説明はしません。

2. nodeのインストール

こちらを参考

3. プロジェクトを作成

npx create-next-app@latest sass-start
$ npx create-next-app@latest sass-start
✔ Would you like to use TypeScript? … No / Yes
✔ Would you like to use ESLint? … No / Yes
✔ Would you like to use Tailwind CSS? … No / Yes
✔ Would you like to use `src/` directory? … No / Yes
✔ Would you like to use App Router? (recommended) … No / Yes
✔ Would you like to customize the default import alias? … No / Yes
✔ What import alias would you like configured? … @/*
Creating a new Next.js app in /home/sondon/dev/wsl/nodejs/react/sass-related/sass-start.
cd sass-start

4. 必要なライブラリをインストール

npm install --save-dev sass
npm install the-new-css-reset
npm install @mui/material @mui/styled-engine-sc styled-components @emotion/react @emotion/styled @mui/icons-material
code .

5. ソースコード

5-1-1. src/scss/common/_variables.scss

$layoutSpacing: 10px;
$headerHeight: 45px;
$sidebarWidth: 200px;

5-1-2. src/scss/common/_mixin.scss

@mixin p-supporting($color: black, $align: left, $size: 15px) {
  color: $color;
  display: block;
  cursor: pointer;
  @if ($color == red) {
    font: {
      weight: bold;
      size: $size;
    }
  } @else if($color == blue) {
    font: {
      size: $size;
    }
    text: {
      align: $align;
    }
  } @else {
    cursor: help;
  }
}

5-1-3. src/scss/common/_mq.scss

$breakpoints: (
  "sm": "screen and (min-width: 480px)",
  "md": "screen and (min-width: 600px)",
  "lg": "screen and (min-width: 960px)",
  "xl": "screen and (min-width: 1280px)"
) !default;

@mixin mq($breakpoint: md) {
  @media #{map-get($breakpoints, $breakpoint)} {
    @content;
  }
}

5-1-4. src/scss/common/_index.scss

@forward "variables";
@forward "mixin";
@forward "mq";

5-2-1. src/lib/common/definitions.ts

export type SidebarLinkType = {
  label: string;
  path: string;
  icon: any;
  targetSegment: string | null;
};

5-2-2. src/lib/common/sidebar-links.tsx

import HomeIcon from "@mui/icons-material/Home";
import AdjustIcon from "@mui/icons-material/Adjust";

import { SidebarLinkType } from "./definitions";

const sidebarHome: SidebarLinkType = {
  label: "Home",
  path: "/",
  icon: <HomeIcon />,
  targetSegment: null,
};
const sidebarComponents: SidebarLinkType = {
  label: "Components",
  path: "/components",
  icon: <AdjustIcon />,
  targetSegment: "components",
};
export const sidebarLinks: SidebarLinkType[] = [sidebarHome, sidebarComponents];

5-2-3. src/lib/footer.tsx

import styles from "../app/layout.module.scss";

const Footer = () => {
  return (
    <footer className={styles.main_footer}>Copyright © 2023 isub</footer>
  );
};

export default Footer;

5-2-4. src/lib/header.tsx

import Image from "next/image";
import styles from "../app/layout.module.scss";

const Header = () => {
  return (
    <div className={styles.main_header}>
      <Image
        className={styles.logo}
        src="/next.svg"
        alt="Next.js Logo"
        width={180}
        height={37}
        priority
      />
    </div>
  );
};

export default Header;

5-2-5. src/lib/sidebar.tsx

"use client";

import Link from "next/link";
import { useSelectedLayoutSegment } from "next/navigation";

import styles from "../app/layout.module.scss";
import { sidebarLinks } from "./common/sidebar-links";
import { getWhichSelected } from "../lib/utils/util";

const Sidebar = () => {
  const activeSegment = useSelectedLayoutSegment();

  return (
    <aside className={styles.main_sidebar}>
      <p>
        <a className={styles.red}>Hello red</a>
      </p>
      <p>
        <a className={styles.blue}>Hi blue</a>
      </p>
      <p>
        <a>Hey there</a>
      </p>
      <hr />
      <ul>
        {sidebarLinks.map((link, idx) => {
          return (
            <li key={idx}>
              {link.icon}
              <Link
                href={link.path}
                className={getWhichSelected(
                  activeSegment,
                  link.targetSegment,
                  styles.selected,
                  styles.unselected
                )}
              >
                {link.label}
              </Link>
            </li>
          );
        })}
      </ul>
    </aside>
  );
};

export default Sidebar;

5-2-6. src/lib/utils/util.ts

export const getWhichSelected = (
  x: string | null,
  y: string | null,
  selectedClass: string,
  unselectedClass: string
): string => (x === y ? selectedClass : unselectedClass);

5-3-1. src/app/globals.scss

html,
body {
  max-width: 100vw;
  overflow-x: hidden;
}

5-3-2. src/app/layout.module.scss

@use "../scss/common/index" as *;

.main {
  height: 100%;
  box-sizing: border-box;
}

.main_header {
  background-color: lightgoldenrodyellow;
  height: $headerHeight - 10px;
  @include mq(lg) {
    height: $headerHeight + 10px !important;
  }
  padding: $layoutSpacing;
}

.main_container {
  height: calc(100% - $headerHeight);
  display: flex;

  & .main_sidebar {
    background-color: #f4f6f9;
    width: $sidebarWidth;
    padding: $layoutSpacing;
    word-break: break-all;

    & p:hover {
      @include p-supporting;
      & .red {
        @include p-supporting($color: red, $size: 20px);
      }
      & .blue {
        @include p-supporting($color: blue, $align: center, $size: 30px);
      }
    }

    & hr {
      background-color: darkgrey;
      height: 1px;
      border: none;
      margin: 10px 0px 10px 0px;
    }

    & ul {
      & li {
        display: flex;
        align-items: center;
        margin: 10px;
        & .unselected {
          text-decoration: none;
        }
        & .selected {
          text-decoration: underline;
          font: {
            weight: bold;
            style: italic;
          }
        }
      }
    }
  }

  & .main_contents {
    min-height: 500px;
    width: calc(100% - $sidebarWidth);
    padding: $layoutSpacing;
  }
}

.main_footer {
  background-color: darkgrey;
  text-align: center;
  padding: $layoutSpacing;
  color: white;
}

5-3-3. src/app/layout.tsx

import "the-new-css-reset/css/reset.css";
import "./globals.scss";
import type { Metadata } from "next";
import { Noto_Sans_JP, Inter } from "next/font/google";

import styles from "./layout.module.scss";
import Header from "../lib/header";
import Footer from "../lib/footer";
import Sidebar from "../lib/sidebar";

const inter = Inter({ subsets: ["latin"] });
const notoSansJp = Noto_Sans_JP({
  weight: ["400", "500", "700"],
  subsets: ["latin"],
  display: "swap",
});

export const metadata: Metadata = {
  title: "Next.js Exercise",
  description: "Sondon at isub",
};

const RootLayout = ({ children }: { children: React.ReactNode }) => {
  return (
    <html lang="en">
      <body className={`${notoSansJp.className} ${inter.className}`}>
        <main className={styles.main}>
          <Header />
          <div className={styles.main_container}>
            <Sidebar />
            <div className={styles.main_contents}>{children}</div>
          </div>
          <Footer />
        </main>
      </body>
    </html>
  );
};

export default RootLayout;

5-3-4. src/app/page.module.scss

.home {
  text-align: center;
  position: absolute;
  top: 25%;
  left: 40%;
}

5-3-5. src/app/page.tsx

import scss from "./page.module.scss";

const Home = () => {
  return (
    <div className={scss.home}>
      <p>下記をベースにサンプルを書いていきます。</p>
      <ul>
        <li>Next</li>
        <li>React</li>
        <li>Redux</li>
        <li>Typescript</li>
        <li>Sass</li>
      </ul>
    </div>
  );
};

export default Home;

5-3-6. src/app/components/page.module.scss

.components {
  color: blue;
}

5-3-7. src/app/components/page.tsx

import scss from "./page.module.scss";

const Components = () => {
  return <div className={scss.components}>Components</div>;
};

export default Components;

6. サーバーを起動

npm run dev

7. ブラウザで確認

  • http://localhost:3000

8. ディレクトリの構造

.
├── README.md
├── next-env.d.ts
├── next.config.js
├── package-lock.json
├── package.json
├── public
│   ├── next.svg
│   └── vercel.svg
├── src
│   ├── app
│   │   ├── components
│   │   │   ├── page.module.scss
│   │   │   └── page.tsx
│   │   ├── favicon.ico
│   │   ├── globals.scss
│   │   ├── layout.module.scss
│   │   ├── layout.tsx
│   │   ├── page.module.scss
│   │   └── page.tsx
│   ├── lib
│   │   ├── common
│   │   │   ├── definitions.ts
│   │   │   └── sidebar-links.tsx
│   │   ├── footer.tsx
│   │   ├── header.tsx
│   │   ├── sidebar.tsx
│   │   └── utils
│   │       └── util.ts
│   └── scss
│       └── common
│           ├── _index.scss
│           ├── _mixin.scss
│           ├── _mq.scss
│           └── _variables.scss
└── tsconfig.json

9 directories, 26 files

9. 備考

Next.jsやSass・Typescriptを使い、シンプルなレイアウトを構成する内容でした。

10. 参考

投稿者プロフィール

Sondon
開発好きなシステムエンジニアです。
卓球にハマってます。

関連記事

  1. 【NextJS】OAuth authentication with A…

  2. 【NextJS】Firestore

  3. 【NextJS】Error Handling

  4. 【NextJS】Streaming with Suspense

  5. 【NextJS】Hooks-useEffect

  6. 【NextJS】ChatApp with Realtime updat…

最近の記事

  1. raspberrypi

制作実績一覧

  1. Checkeys