【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. 参考

関連記事

  1. 【NextJS】Checkbox・Radio Group

  2. 【NextJS】OAuth authentication with A…

  3. 【NextJS】NextJS・TypeScript・Apollo Cl…

  4. 【NextJS】Firestore

  5. 【NextJS】Events

  6. 【NextJS】Checkbox・Radio・Select

最近の記事

  1. AWS
  2. AWS
  3. AWS
  4. AWS
  5. AWS
  6. AWS
  7. AWS
  8. AWS

制作実績一覧

  1. Checkeys