Open8

Next.js(app router)+ヘッドレスWordpress+shadcn/uiで作るコーポレートサイト|セットアップ編

オオモリオオモリ
  1. npx create-next-app@latestでnext.js立ち上げ(https://nextjs.org/docs/getting-started/installation)
    全てYES

※この時点で、ESlintが入っている。

{
  "name": "caen-hp",
  "version": "0.1.0",
  "private": true,
  "scripts": {
    "dev": "next dev",
    "build": "next build",
    "start": "next start",
    "lint": "next lint"
  },
  "dependencies": {
    "react": "^18",
    "react-dom": "^18",
    "next": "14.2.3"
  },
  "devDependencies": {
    "typescript": "^5",
    "@types/node": "^20",
    "@types/react": "^18",
    "@types/react-dom": "^18",
    "postcss": "^8",
    "tailwindcss": "^3.4.1",
    "eslint": "^8",
    "eslint-config-next": "14.2.3"
  }
}
オオモリオオモリ
  1. Gitで管理できるようにする
    1. Gitでレポジトリを作る
      ※この時gitignoreも作る。nodeで。
    2. 下記コマンドを該当レポジトリで打つ。
> git init
> git remote add origin https://~(リモートリポジトリのURL).git
> git add .
> git commit -m "first commit"
> git push origin main

※GitHubにpushする時にerror: failed to push some refs と表示されてpushできない時

git push -f origin master

※※error: failed to push some refs to~~ が出たらもう一度git push -f origin masterする

オオモリオオモリ

基本的なディレクトリ構造。

├─ app/
├─ components/
│  ├─ elements/
│  │  └─ Ex)Button
│  │     └─ Ex)Button.tsx
│  └─ layouts/
│     └─ Ex)Header
│        └─ Ex)Header.tsx
│  └─ ui/
│     └─ Ex)shadcn/uiで自動インポートされるコンポーネント
├─ features/
│  └─ Ex)/post
│     ├─ api/
│   └─ Ex)getPost.ts
│     ├─ styles/
│     ├─ components/
│        ├─ Ex)Post.tsx
│        └─ Ex)Posts.tsx
│     ├─ hooks/
│        └─ Ex)usePost.ts
│     └─ types/
│        └─ Ex)index.ts
├─ hooks/
├─ styles/
├─ types/
├─ lib/
└─ utils/
オオモリオオモリ

tsconfig.jsonでエラーが出ていたので、
"moduleResolution": "bundler", -> "moduleResolution": "node",
に変更

オオモリオオモリ

上記の記事を部分的に参考にして、ESlint,Pritter,Stylelint,VScodeの設定を行なっていく。
まずは必要プラグインのインポート。

  1. Pritter
    npm install -D prettier prettier-plugin-tailwindcss eslint-config-prettier
  2. ESlint
    npm install -D eslint-plugin-import eslint-plugin-unused-imports eslint-plugin-simple-import-sort
    ※eslint-plugin-prettierは非推奨ならしい。https://note.com/eikichi_/n/n89ebfe500fc1
  3. Stylelint
    npm install -D stylelint-scss stylelint-prettier stylelint-config-recommended-scss stylelint-config-recess-order
オオモリオオモリ

設定ファイルを作成していく。内容は色々拾ってきただけのもので、ブラッシュアップ可能。

  1. tailwind.config.tsは、shadcn/uiを入れなければ変えなくても良さそう

  2. stylelint.config.js

stylelint.config.jsの内容
module.exports = {
  plugin: ["stylelint-scss"],
  extends: [
    "stylelint-config-recommended-scss", // scssのための拡張ルール追加
    "stylelint-config-recess-order", // 視認性を考慮したcssプロパティの自動ソートを設定
    "stylelint-config-prettier", // Prettierとの競合ルールをOFFにする
  ],
};
  1. prettier.config.js
prettier.config.jsの内容
module.exports = {
  printWidth: 80, // 1行で表示する文字数
  tabWidth: 2, // インデントのサイズ
  useTabs: false, // インデントにスペースの代わりにタブを使うかどうか
  semi: true, // 文の後にセミコロンを付けるかどうか
  singleQuote: false, // 文字列をシングルクォートで囲むかどうか(falseだとダブルクォート)
  quoteProps: "as-needed", // オブジェクトのプロパティ名をクォートで囲むかどうか
  jsxSingleQuote: false, // JSX内のクォートをシングルクォートで囲むかどうか
  trailingComma: "es5", // 複数行のときの末尾のカンマを付けるかどうか
  bracketSpacing: true, // オブジェクトリテラルの{}内の前後にスペースを入れるかどうか
  bracketSameLine: false, // JSX内の要素の閉じタグを最後の行に含んで表示するか
  arrowParens: "always", // アロー関数の引数が1つのときにカッコで囲むかどうか
};
  1. VSCode(Cursol)の設定を統一させるために.vscode/settings.jsonファイルを作成する。
.vscode/settings.jsonの内容
{
  // ファイル保存時のフォーマット処理を有効化する
  "editor.formatOnSave": true,

  // 各ファイルのフォーマッターを指定する
  "[css]": {
    "editor.defaultFormatter": "esbenp.prettier-vscode"
  },
  "[scss]": {
    "editor.defaultFormatter": "esbenp.prettier-vscode"
  },
  "[javascript]": {
    "editor.defaultFormatter": "esbenp.prettier-vscode"
  },
  "[typescript]": {
    "editor.defaultFormatter": "esbenp.prettier-vscode"
  },
  "[json]": {
    "editor.defaultFormatter": "esbenp.prettier-vscode"
  },

  // Stylelintの対象を設定
  "stylelint.validate": ["css", "scss"],

  // ESLintの対象を設定
  "eslint.validate": ["javascript", "javascriptreact", "typescript", "typescriptreact"],

  // 保存時に実行される各コードアクションを有効化する
  "editor.codeActionsOnSave": {
    "source.fixAll.stylelint": "always",
    "source.fixAll.eslint": "always",
    "source.organizeImports": "always"
  },

  // VSCodeデフォルトのLintを無効にする(各Lintのルールで統一する際に設定)
  "css.validate": false,
  "scss.validate": false,
  "javascript.format.enable": false,
  "typescript.format.enable": false
}
  1. .eslintrc.js
.eslintrc.jsの内容
module.exports = {
  extends: [
    "eslint:recommended",
    "next",
    "next/core-web-vitals",
    "plugin:@typescript-eslint/eslint-recommended",
    "plugin:@typescript-eslint/recommended",
    "plugin:prettier/recommended",
    "plugin:redos/recommended",
  ],
  plugins: ["simple-import-sort", "import"],
  rules: {
    "@next/next/no-img-element": "error",
    "@next/next/no-page-custom-font": "error",
    "@typescript-eslint/ban-ts-comment": "off",
    "@typescript-eslint/ban-ts-ignore": "off",
    "@typescript-eslint/explicit-function-return-type": "off",
    "@typescript-eslint/no-floating-promises": "error",
    "@typescript-eslint/no-explicit-any": "off",
    curly: ["error"],
    eqeqeq: ["error", "always", { null: "ignore" }],
    "import/first": "error",
    "import/newline-after-import": "error",
    "import/no-duplicates": "error",
    "import/no-extraneous-dependencies": ["error"],
    "import/no-unresolved": "error",
    "no-console": "error",
    "no-debugger": "error",
    "react-hooks/exhaustive-deps": "warn",
    "react-hooks/rules-of-hooks": "error",
    "react/jsx-key": [
      "error",
      {
        checkFragmentShorthand: true,
        checkKeyMustBeforeSpread: true,
        warnOnDuplicates: true,
      },
    ],
    "react/jsx-no-bind": [
      "error",
      {
        allowArrowFunctions: true,
        allowBind: false,
        allowFunctions: false,
        ignoreDOMComponents: false,
        ignoreRefs: false,
      },
    ],
    "react/no-unknown-property": ["error", { ignore: ["custom-element"] }],
    "react/prop-types": "off",
    "react/react-in-jsx-scope": "off",
    "redos/no-vulnerable": "error",
    "simple-import-sort/exports": "error",
    "simple-import-sort/imports": "error",
    "sort-keys": [
      "error",
      "asc",
      { caseSensitive: true, minKeys: 2, natural: false },
    ],
  },
  // ...
};
オオモリオオモリ

shadcn/uiを使いたいので、インポートする。

npx shadcn-ui@latest init
✔ Which style would you like to use? › New York
✔ Which color would you like to use as base color? › Slate
✔ Would you like to use CSS variables for colors? … no / yes
オオモリオオモリ

メタデータ設定

src/data/metadata.ts
export const COMPANY_NAME = "????????????";
export const SITE_TITLE = "????????????";
export const SITE_DESCRIPTION = "????????????";
export const SITE_URL = "https://xxxxx.co.jp";
export const SITE_NAME = "????????????";
export const TWITTER_HANDLE = "";
export const OGP_URL = "/ogp/img_ogp.png";

export const metadata = {
 title: SITE_TITLE,
 description: SITE_DESCRIPTION,
 metadataBase: {
   url: SITE_URL,
 },
 keywords: [COMPANY_NAME, "?????", "???????"],
 authors: [{ name: COMPANY_NAME, url: SITE_URL }],
 openGraph: {
   title: SITE_TITLE,
   description: SITE_DESCRIPTION,
   url: SITE_URL,
   siteName: SITE_NAME,
   images: [
     {
       alt: SITE_TITLE,
       url: OGP_URL,
       width: 1200,
       height: 630,
     },
   ],
   locale: "ja_JP",
   type: "website",
 },
 twitter: {
   card: "summary_large_image",
   title: SITE_TITLE,
   description: SITE_DESCRIPTION,
   site: TWITTER_HANDLE,
   creator: TWITTER_HANDLE,
   images: [
     {
       alt: SITE_TITLE,
       height: 675,
       url: OGP_URL,
       width: 1200,
     },
   ],
 },
 icons: {
   icon: "/favicon.ico",
   shortcut: "/favicon-16x16.png",
   apple: "/apple-touch-icon.png",
 },
};
layout.tsx
`import Footer from "@/components/layouts/Footer";
import Header from "@/components/layouts/Header";
import { metadata } from "@/data/metadata";
import { Noto_Sans_JP } from "next/font/google";
import "./globals.css";

const zenKakuGothicNew = Noto_Sans_JP({
  subsets: ["latin"],
  weight: ["500", "700"], // medium and bold
});

export async function generateMetadata() {
  return metadata;
}

export default function RootLayout({ children }: Readonly<{ children: React.ReactNode }>) {
  return (
    <html lang="ja">
      <body className={zenKakuGothicNew.className}>
        <Header />
        {children}
        <Footer />
      </body>
    </html>
  );
}