💅ECサイトモックを Next.js で立ち上げるワークアウト経験
モチベーション
👩「将来ネイルチップ💅販売するためのサイト作って!」
と SPA アプリ開発屋が妻に言われ てしまい 、楽に立ち上げられないかと模索中、
というニーズにとてもマッチしたブックがリリースされていたので、参考にさせて頂きながら作ってみることにしました。
この記事は作ってみた際のピックアップメモとなります。
※2022/05/06 時点の上記ブックを参考にしているため、内容やチャプター名が変更になっている可能性があります。
環境や方向性
- モックなので、お金を掛けたくない(大事)
-
ホスティングは Vercel に任せるワークアウトなので、ローカルでサーバ動作させるのみに留めます - 商品管理(バックエンド)は Stripe に任せる
-
- モック時間を掛けたくない
- 他にもしたいことがいっぱいあるから…😅
- 設計は後回しにする
- あまり細かいところは気にせず、セキュリティ等々大事なところは押さえて作る
- デザインは簡易的にする
- 勉強がてら Tailwind CSS を使う
- 他にもしたいことがいっぱいあるから…😅
ブックを読んで進める
02 Next.jsを利用したアプリのセットアップ
内容に加え、以下の環境をセットアップ。
- 各種ツール・言語設定
- TypeScript
- ESLint
- Prettier
- husky, lint-staged (コミット時整形)
- Tailwind CSS
- ディレクトリ整理
TypeScript
npx create-next-app@latest --ts
ESLint, Prettier
npm i -D eslint-config-prettier prettier prettier-plugin-organize-imports
root: true
extends:
- "next"
- "next/core-web-vitals"
- "prettier"
rules:
import/newline-after-import: warn
Prettier を導入しますが、整形ルールは標準で行くので、 .prettierrc.yaml
は作成無しで。
import 文の並びなども整理するようにしています。
husky, lint-staged
npx husky-init && npm i -D husky lint-staged npm-run-all
-npm test
+npm run lint-staged
@@ -6,8 +6,14 @@
"dev": "next dev",
"build": "next build",
"start": "next start",
- "lint": "next lint",
- "prepare": "husky install"
+ "test": "run-p --aggregate-output test:*",
+ "test:next": "next lint",
+ "test:lint": "eslint src --ext ts --ext tsx --max-warnings 0",
+ "test:format": "prettier --check 'src/**/*.{ts,tsx,css}'",
+ "fix": "run-s fix:*",
+ "fix:lint": "eslint src --ext ts --ext tsx --fix",
+ "fix:format": "prettier --write 'src/**/*.{ts,tsx,css}'",
+ "lint-staged": "lint-staged"
},
"dependencies": {
"next": "12.1.6",
@@ -27,5 +33,11 @@
"prettier": "^2.6.2",
"prettier-plugin-organize-imports": "^2.3.4",
"typescript": "4.6.4"
+ },
+ "lint-staged": {
+ "src/**/*.{ts,tsx,css}": [
+ "eslint --fix",
+ "prettier --write"
+ ]
}
-}
+}
\ No newline at end of file
ディレクトリ整理
create-next-app
標準のままでは、設定系ファイルやらソースコードやらが同ディレクトリ階層で管理されるため、 src
ディレクトリを切って pages
, styles
を移動します。
以後発生するソースコードは src
ディレクトリで管理します。
Tailwind CSS
Install Tailwind CSS with Next.js - Tailwind CSS を参考にセットアップ。
module.exports = {
- content: [],
+ content: [
+ "./src/**/*.{js,ts,jsx,tsx}",
+ ],
theme: {
extend: {},
},
03 Next.jsで静的なサイトを作ってみよう
daisyUI を導入
今回は Tailwind を導入しているので、Bootstrap はインストールしません。
ブックでは NavBar
Container
のコンポーネントを使用しており、同様に(楽を)したいため、このタイミングで daisyUI を導入します。
npm i daisyui @tailwindcss/typography
npm i -D eslint-plugin-tailwindcss
# 導入漏れ…
theme: {
extend: {},
},
- plugins: [],
+ plugins: [
+ require("@tailwindcss/typography"),
+ require('daisyui')
+ ],
}
- "next"
- "next/core-web-vitals"
- "prettier"
+ - "plugin:tailwindcss/recommended"
+plugins:
+ - "tailwindcss"
rules:
import/newline-after-import: warn
導入したので _app.tsx
を更新、 _document.tsx
も追加しテーマ設定。
import type { AppProps } from "next/app";
+import Link from "next/link";
import "../styles/globals.css";
function MyApp({ Component, pageProps }: AppProps) {
- return <Component {...pageProps} />;
+ return <>
+ <div className="navbar bg-primary text-primary-content">
+ <Link href="/#home">
+ <a className="btn btn-ghost normal-case text-xl">Hello EC</a>
+ </Link>
+ </div>
+ <Component {...pageProps} />
+ </>
}
export default MyApp;
import { Head, Html, Main, NextScript } from "next/document";
export default function Document() {
return (
<Html lang="ja" data-theme="cupcake">
<Head />
<body>
<Main />
<NextScript />
</body>
</Html>
);
}
07 Stripeに登録した商品を、Next.jsで取得・表示しよう
daisyUI のカードを使ってデザインを整えます。
08 use-shopping-cartで簡単な決済フォームを追加しよう
checkoutSingleItem() の引数が異なる
useShoppingCart
を利用して、checkoutSingleItem
関数を取得しています。
この関数にStripeの料金IDを渡すことで、StripeのCheckoutページに移動できます。
と書かれているのですが、TS 環境だと型で叱られます。
実装を見るとサンプルのように sku
や price
を含んだオブジェクトが引数のようです。
を見ると最近 productId
→ itemsOrPriceId
に更新されたようですね。型定義の更新漏れでした。
あまりよろしくないですが @ts-ignore
で回避します。
(次のチャプターで削除する処理なので、一旦無視も可能)
10 use-shopping-cartで、カート機能を追加しよう
useShoppingCart() から引き出す要素の型解決ができない
TS環境でサンプル通り useShoppingCart()
を使用すると、型が any になっていました。
SelectorResult & CartActions
で戻り値型指定されているんですがなぜでしょう🤔
// useShoppingCart から直接分割代入すると型が any になるため、
// 一旦まるごと参照して分割代入
const shoppingCart = useShoppingCart();
const { cartDetails, removeItem, formattedTotalPrice, clearCart } = shoppingCart
で解決しました。
11Webhookで注文内容を取得しよう
micro の型定義のインストール
TS環境では micro
のインストールだけでは型定義が不足しているので、以下にてインストール。
npm i -D @types/micro
event からのデータ取り出し時に型解決出来ない
決済完了時のイベントから、注文内容を取得する
のサンプルコードが、TS環境だと any
となっていました。
イベント毎にデータ型が変わる
ということだと理解しましたので、以下のような型アサーションで回避…
function extractCheckoutSessionDataObject(event: Stripe.Event): Stripe.Checkout.Session | undefined {
if (event.type.startsWith('checkout.session.')) {
return event.data.object as Stripe.Checkout.Session
}
return undefined
}
const checkoutSessionData = extractCheckoutSessionDataObject(event);
if (checkoutSessionData?.payment_status === 'paid') {
const item = await stripe.checkout.sessions.listLineItems(checkoutSessionData?.id);
// ...
}
ブックの内容を終えて
実装後は以下のようなレイアウトになりました。
Stripe を用いることで、簡単にカート実装が出来て、ECサイト立ち上げのイメージが湧いてきました。
もちろんこのワークアウトの内容だけでは、在庫管理や発送状況の管理などの機能がないので、立ち上げまではまだまだ掛かりそうです。
Tailwind は、クラス名で当てたいスタイルをネーミングセンス無しに当てられるのは新感覚で良いかなと思う反面、 className
が肥大化してコンポーネント内のコード量が増えるのが気になりました。
今回のようなワークアウトの実装だと直感で書けるので採用で、商用になるとメンテナビリティ考慮で CSS Module の方がいいかなぁという所感。
Discussion