導入 これは のスポンサーによるパートナーシップ記事です。 OneEntry CMS e コマース アプリケーションの構築は、多くの場合、困難な作業です。利用可能な代替手段が非常に多いため、プロジェクトの要件、拡張性のニーズ、長期的な持続可能性を満たす技術スタックを選択するのは簡単ではありません。 もう 1 つの重要な点は、e コマース プロジェクトが大量のデータと CRUD 操作を扱うことです。堅牢でスケーラブルで安全なバックエンド システムを作成するには、経験豊富な開発者でも長い時間がかかることがあります。 私は、NextJS、TypeScript、Tailwind CSS、OneEntry CMS に基づく技術スタックを選択しました。私たちは実際的な e コマース プロジェクトを自分たちで構築し、それがどのように連携し、コンテンツ管理を簡素化するためにどのように使用できるかを確認します。 このプロジェクトのコードは、 で入手できます。 GitHub リポジトリ 技術スタックの選択 高速かつ効率的な Web アプリケーションを構築するための React フレームワークであり、クライアントとサーバーのレンダリング、データのフェッチ、ルート ハンドラー、ミドルウェア、組み込みの最適化などの機能が付属しています。 NextJS は、 JavaScript に静的型付けを追加し、e コマースなどのスケーラブルなプロジェクトのエラーを見つけて修正しやすくします。また、オートコンプリートやリファクタリング支援などの機能により生産性も向上します。 TypeScript は 、Web アプリのスタイル部分を高速化し、開発者が外部 CSS ファイルを切り替えたり、それぞれのクラス名を考えたりすることなく、マークアップ内の要素をスタイル設定できるようにします。 Tailwind CSS は 、使いやすいインターフェイス、簡単に拡張できるバックエンド、高速 API、わかりやすいドキュメントを備えたヘッドレス コンテンツ管理システムで、Web サイトのコンテンツ作成と管理エクスペリエンスの生産性を向上させます。 OneEntry CMS は コンテンツとデザイン ランディング ページには見出しタイトルが表示され、ショップの特徴がリストされ、ヒーロー画像が含まれます。 最初のショップセクションは衣料品に特化します。 2 番目のショップ セクションにはギアが含まれます。 各項目には、詳細を含む個別のプレビュー ページがあります。 すでにカートに入っている商品には、削除するオプションがあります。 カートには選択したすべてのアイテムがリストされ、合計が計算されます。 OneEntry プロジェクトの作成 まず、ユーザーは新しいアカウントにサインアップする必要があります。これを行うには、OneEntry ホームページに移動し、電子メール アカウント経由で 。 サインアップします その後、 と、OneEntry ダッシュボードが表示されます。 ログインする 新しいプロジェクトを作成することから始めます。 Study プランを 1 か月間使用するための無料コードを受け取ります。プロジェクトの作成プロセス中にアクティブ化する機会があります。 プロジェクトの作成には数分かかります。準備が完了すると、プロジェクトのステータスが「作業中」に変わり、ステータス インジケーターが緑色になります。 ページの作成 プロジェクトの作成後、CMS ポータルにアクセスしてアプリのデータを作成および保存するためのログイン詳細が記載された電子メールが届きます。 ログイン後、最初のページを作成できるようになります。 [コンテンツ管理] に移動し、[新しいページの作成] をクリックして、ページの種類、ページ タイトル、ページ ULR、メニュー項目の名前など、必要なデータをすべて入力します。 入力するとすべてのデータが自動的に保存されます。 ホーム、衣類、ギア、カートの 4 つの異なるページを作成します。作成すると、ページは以下のスクリーンショットのようになります。 属性の作成 次に、保存するデータ構造を作成する必要があります。 OneEntry CMS では、データの属性を作成することによってこれが実現されます。 「設定」に移動し、水平メニューで「属性」を選択します。名前、マーカー、タイプを指定するホームページの属性セットを作成します。 作成すると、以下のスクリーンショットのようになります。 同様に、衣類とギアに 2 つの個別の属性セットを作成してみましょう。作成すると、結果は以下のスクリーンショットのようになります。 次に、各セットに特定の属性を定義しましょう。 先ほどホーム セクションのワイヤーフレームに含めたコンテンツに基づいて、タイトル、説明、画像を表示したいと思います。 ホームの歯車アイテムをクリックし、以下のリストに示すように属性名、マーカー、および属性タイプを作成します。 次に、戻って「衣類」の歯車アイコンをクリックします。 製品のタイトル、サブタイトル、説明、画像、価格を表示したいため、このページの属性は少し異なります。 属性構造は次のようになります。 次に、同じ構造を使用する Gear ページに対して同じことを行います。 コンテンツの追加 プロジェクトのこの段階では、コンテンツ構造がすでに定義されており、コンテンツ自体の作成を開始する準備ができています。 以前にサイトのすべてのページを作成したコンテンツ管理セクションに移動します。 ホームの編集ボタンをクリックします。その後、水平メニューの「属性」タブをクリックします。 属性セットとして「ホーム」を選択します。これにより、ホームページの設定で以前に作成したすべての属性がロードされます。 次に、ホームページに表示するサンプル データを入力します。 次に、衣類とギアのページにコンテンツを追加しましょう。 ページ タイプとしてカタログを選択したため、左側のメニューから [カタログ] を選択すると、両方のページがそこに表示されるはずです。 ここで、「衣類」の「追加」アイコンをクリックし、いくつかのアイテムを追加します。 まず、追加する製品のヘッダーを追加します。 次に、「属性」タブに切り替え、属性セットとして「衣類」を選択し、必要なデータを入力します。 カタログ メニューに戻り、衣類とギアの両方のアイテムをさらにいくつか表示します。デモ アプリでは、以下のスクリーンショットに示すように 4 つの項目を追加しました。 APIアクセストークンを作成する OneEntry CMS で作成されたデータはすべて保護されているため、アクセスできるようにするにはプライベート トークンを作成する必要があります。 これを行うには、[設定] に移動し、[アプリ トークン] を選択します。アプリ名と有効期限を入力し、「作成」をクリックします。これにより、一意の API キーが生成されます。 アクション リストの表示アイコンをクリックすると、キーが表示されます。チュートリアルの次のセクションで必要になるため、これをクリップボードにコピーします。 NextJS プロジェクトのセットアップ チュートリアルのこのセクションでは、コードの操作を開始し、OneEntry CMS で動作するように NextJS プロジェクトを構成します。 ターミナルを開き、コマンド を実行します。 npx create-next-app@latest CLI によりセットアップ ウィザードが開始されます。プロジェクトの名前を入力し、以下に示すようにデフォルト値をすべて選択します。 セットアップが完了するまで 1 分ほどかかります。NextJS アプリが作成されると通知が届きます。 その後、コマンド を使用してディレクトリを新しく作成したフォルダーに変更し、 を実行して開発者サーバーを起動します。 cd winter-sports npm run dev アクセスするには、ターミナルに表示されるリンクをクリックするか、Web ブラウザを開いて に手動で移動します。 http://localhost:3000 NextJS 開発者サーバーのランディング ページが表示されます。 次に、アプリに必要な環境値を作成しましょう。コード エディターに戻り、プロジェクトのルートに ファイルを作成します。 .env 前にクリップボードにコピーした API キーを次のように貼り付けます。 API_KEY=your-api-code-from-oneentry これにより、API 呼び出しを行って OneEntry CMS からデータを取得すると、 経由でキーにアクセスできるようになります。 process.env.API_KEY また、外部ドメインからのメディアを含めることができるように NextJS を構成する必要もあります。これは、OneEntry CMS から画像にアクセスするために必要になります。 プロジェクト ルートにあるファイル を開き、次のように編集します。 next.config.js const nextConfig = { images: { remotePatterns: [ { hostname: "ecommerce.oneentry.cloud", }, ], }, }; module.exports = nextConfig; 最後に、すべてのスタイルを最初から作成するため、アプリの Tailwind のデフォルト スタイルをリセットする必要があります。 フォルダー内の ディレクトリにある ファイルを開き、ファイルの内容を次のように変更します。 src app globals.css @tailwind base; @tailwind components; @tailwind utilities; タイプの作成 TypeScript を使用するので、アプリケーションで使用するデータ型を定義する必要があります。 ページやコンポーネント内でこれを行うこともできますが、コードをクリーンに保ち、繰り返しを避けるために、 ディレクトリ内に新しいフォルダー を作成します。新しく作成したフォルダー内にファイル を作成し、コードを含めます。 app interfaces data.tsx export interface Product { id: string; category: string; title: string; subtitle: string; description: string; image: string; price: number; } export interface ProductAPI { id: string; attributeValues: { en_US: { producttitle: { value: { htmlValue: string }[]; }; productsubtitle: { value: { htmlValue: string }[]; }; productdescription: { value: { htmlValue: string }[]; }; productimage: { value: { downloadLink: string }[]; }; productprice: { value: number; }; }; }; } export interface Page { pageUrl: string; title: string; description: string; image: string; localizeInfos: { en_US: { title: string; }; }; } export interface PageAPI { attributeValues: { en_US: { herotitle: { value: { htmlValue: string }[]; }; herodescription: { value: { htmlValue: string }[]; }; heroimage: { value: { downloadLink: string }[]; }; }; }; } export interface URLProps { params: { category: string; productId: string; }; } export interface TextProps { className: string; text: string; } 製品とページのデータは両方とも、フロントエンドのレンダリング データ構造の型と、フェッチ メソッドを介した API からの応答を持ちます。 また、URL パラメーターからのデータのデータ型と、CMS のテキスト入力フィールドから受信したデータのテキスト レンダラーを定義しました。 APIフェッチ関数の作成 次に、OneEntry CMS と通信してページと商品のデータを取得するために使用する関数をいくつか作成しましょう。 繰り返しますが、これを各ファイルで行うこともできますが、コードをわかりやすくするために、 ディレクトリ内に新しいフォルダー 作成し、その中にファイル を作成しましょう。 app services fetchData.tsx export async function getPages() { const response = await fetch( "https://ecommerce.oneentry.cloud/api/content/pages", { method: "GET", headers: { "x-app-token": `${process.env.API_KEY}`, }, } ); return await response.json(); } export async function getProducts(category: string) { const response = await fetch( `https://ecommerce.oneentry.cloud/api/content/products/page/url/${category}?limit=4&offset=0&langCode=en_US&sortOrder=DESC&sortKey=id`, { method: "GET", headers: { "x-app-token": `${process.env.API_KEY}`, }, } ); return await response.json(); } export async function getProduct(id: string) { const response = await fetch( `https://ecommerce.oneentry.cloud/api/content/products/${id}`, { method: "GET", headers: { "x-app-token": `${process.env.API_KEY}`, }, } ); return await response.json(); } 関数は、OneEntry CMS で作成したすべてのページに関するデータを取得します。 getPages 関数は、 パラメーターに基づいて製品の特定のコレクションのデータをフェッチします。関数を製品ページにインポートするときにパラメーターを渡します。 getProducts category 関数は、開いた製品の に基づいてデータを取得します。特定の製品のプレビュー ページに関数をインポートするときにパラメーターを渡します。 getProduct id を使用して、OneEntry CMS へのアクセスを認証するために ファイルで定義した API キーにアクセスしていることに注目してください。 process.env.API_KEY .env ヘルパー関数の作成 また、まだ フォルダー内にいますが、その中に という名前の別の新しいファイルを作成しましょう。これには小さなユーティリティ関数が含まれます。 services helpers.tsx export function calculateTotal(items: { price: number }[]) { return items.reduce((total, item) => total + Number(item.price), 0); } export function boughtStatus(items: { id: string }[], id: string) { return items.some((item) => item.id === id); } export function cartIndex(items: { id: string }[], id: string) { return items.findIndex((item) => item.id === id); } 関数は、カートに追加された製品の価格を合計し、合計値を返します。 calculateTotal 、プレビュー ルート内の個々のアイテムが既にカートに追加されているかどうかを検出します。 boughtStatus は、カートに追加された製品の配列内の項目の位置を検出します。 cartIndex コンポーネントの作成 ディレクトリに戻り、その中に新しいフォルダー を作成します。 app components 新しく作成したフォルダーを開き、その中に 7 つの個別のファイル ( 、 、 、 、 、 、 ) を含めます。 Header.tsx Footer.tsx Text.tsx Card.tsx Preview.tsx Order.tsx AddToCart.tsx ヘッダーコンポーネント ファイル を開き、次のコードを含めます。 Header.tsx import Link from "next/link"; import { Page } from "../interfaces/data"; export default function Header({ pages }: { pages: Page[] }) { return ( <div className="flex justify-between items-center mb-10 p-6"> <Link href="/"> <h1 className="text-xl">🏂 Alpine Sports</h1> </Link> <div className="flex space-x-4 list-none"> {pages.map((page, index: number) => ( <Link key={index} href={page.pageUrl === "home" ? "/" : `/${page.pageUrl}`} > {page.localizeInfos.en_US.title} </Link> ))} </div> </div> ); } ヘッダーには会社名を表示し、コンポーネントがページにインポートされると API から取得するナビゲーション リンクをループします。 2 列のレイアウトを作成し、両方の要素を画面の反対側に水平に配置して、典型的なナビゲーションの外観を実現しました。 フッターコンポーネント ファイル を開き、次のコードを含めます。 Footer.tsx export default function Footer() { return ( <div className="text-center mt-auto p-6"> <h1>Alpine Sports, Inc.</h1> <p>All rights reserved, {new Date().getFullYear()}</p> </div> ); } フッターには、会社のサンプル名と今年のコンテンツの権利を含めました。コンテンツを中央に配置し、パディングを追加しました。 テキストコンポーネント ファイルを開き、次のコードを含めます。 Text.tsx import { TextProps } from "../interfaces/data"; export default function Text({ className, text }: TextProps) { return ( <div className={className} dangerouslySetInnerHTML={{ __html: text }} /> ); } Text コンポーネントは、OneEntry CMS から受信したテキスト データをレンダリングし、HTML タグなしでアプリケーションに適切に表示します。 カードコンポーネント ファイルを開き、次のコードを含めます。 Card.tsx import Link from "next/link"; import Text from "../components/Text"; import { Product } from "../interfaces/data"; export default function Card({ product }: { product: Product }) { return ( <Link href={`/${product.category}/${product.id}`}> <div className="group relative"> <div className="group-hover:opacity-75 h-80"> <img src={product.image} alt="Product card image" className="h-full w-full object-cover object-center" /> </div> <div className="mt-4 flex justify-between"> <div> <h3 className="text-sm text-gray-700"> <Text className="" text={product.title} /> </h3> <Text className="mt-1 text-sm text-gray-500" text={product.subtitle} /> </div> <p className="text-sm font-medium text-gray-900">${product.price}</p> </div> </div> </Link> ); } カードコンポーネントには、各商品の画像、タイトル、サブタイトル、価格を表示しました。ページにインポートされたら、すべての項目をマッピングします。 画像はカードの上部に表示され、その後にタイトルと説明が表示され、コンポーネントの右下に価格が表示されます。 プレビューコンポーネント ファイル を開き、次のコードを含めます。 Preview.tsx "use-client"; import Image from "next/image"; import Text from "./Text"; import { Product } from "../interfaces/data"; export default function Preview({ children, productItem, }: { children: React.ReactNode; productItem: Product; }) { return ( <div className="flex mx-auto max-w-screen-xl"> <div className="flex-1 flex justify-start items-center"> <Image src={productItem.image} alt="Product preview image" width="450" height="900" /> </div> <div className="flex-1"> <Text className="text-5xl pb-8" text={productItem.title} /> <Text className="text-4xl pb-8 text-gray-700" text={`$${productItem.price}`} /> <Text className="pb-8 text-gray-500 text-justify" text={productItem.description} /> {children} </div> </div> ); } プレビュー コンポーネントは、ユーザーがクリックすると各製品に関する詳細情報を表示するために使用されます。 商品画像、タイトル、価格、説明が表示されます。レイアウトは 2 つの列に分割され、画像が左の列に表示され、残りのコンテンツが右の列に表示されます。 注文コンポーネント ファイル を開き、次のコードを含めます。 Order.tsx "use client"; import { useState, useEffect } from "react"; import Link from "next/link"; import Image from "next/image"; import Text from "./Text"; import { calculateTotal } from "../services/helpers"; import { Product } from "../interfaces/data"; export default function Order() { const [cartItems, setCartItems] = useState<Product[]>([]); useEffect(() => { const storedCartItems = localStorage.getItem("cartItems"); const cartItems = storedCartItems ? JSON.parse(storedCartItems) : []; setCartItems(cartItems); }, []); return ( <div> {cartItems.map((item, index) => ( <div key={index} className="flex items-center border-b border-gray-300 py-2" > <div className="w-20 h-20 mr-12"> <Image src={item.image} alt={item.title} width={80} height={80} /> </div> <div> <Link href={`/${item.category}/${item.id}`} className="text-lg font-semibold" > <Text className="" text={item.title} /> </Link> <Text className="text-gray-600" text={item.subtitle} /> <p className="text-gray-800">Price: ${item.price}</p> </div> </div> ))} <div className="mt-4 text-end"> <h2 className="text-xl font-semibold mb-8"> Total Amount: ${calculateTotal(cartItems)} </h2> <button className="bg-blue-500 hover:bg-blue-700 py-2 px-8 rounded"> Proceed to checkout </button> </div> </div> ); } 注文コンポーネントには、ユーザーがカートに追加したすべての商品がリストされます。各商品の画像、タイトル、サブタイトル、価格が表示されます。 コンポーネントがレンダリングされると、アプリは現在カート内にあるすべてのアイテムにアクセスし、それらを 状態変数に設定し、 メソッドを介して画面にレンダリングします。 cardItems map レンダリングされたアイテムの合計量は、 ファイルからインポートした 関数によって計算されます。 helpers.tsx calculateTotal カートに追加コンポーネント ファイル を開き、次のコードを含めます。 AddToCart.tsx "use client"; import React, { useState, useEffect } from "react"; import { boughtStatus, cartIndex } from "../services/helpers"; import { Product } from "../interfaces/data"; export default function AddToCart({ category, id, title, subtitle, image, price, }: Product) { const storedCartItems = JSON.parse(localStorage.getItem("cartItems") || "[]"); const isPurchased = boughtStatus(storedCartItems, id); const indexInCart = cartIndex(storedCartItems, id); const [btnState, setBtnState] = useState(false); useEffect(() => { isPurchased && setBtnState(true); }, []); const handleButtonClick = () => { const updatedCartItems = [...storedCartItems]; if (!btnState && !isPurchased) { updatedCartItems.push({ category, id, title, subtitle, image, price }); } else if (isPurchased) { updatedCartItems.splice(indexInCart, 1); } localStorage.setItem("cartItems", JSON.stringify(updatedCartItems)); setBtnState(!btnState); }; return ( <button className={`${ !btnState ? "bg-blue-500 hover:bg-blue-600" : "bg-yellow-300 hover:bg-yellow-400" } py-2 px-8 rounded`} onClick={handleButtonClick} > {!btnState ? "Add to Cart" : "Remove from Cart"} </button> ); } addToCart コンポーネントは個々の製品プレビュー ページに表示され、ユーザーが製品をショッピング カートに追加できるようになります。 レンダリング時に、 関数は、製品が既にカートに追加されているかどうかを検出します。レンダリングされたボタンではない場合は「カートに追加」と表示され、それ以外の場合は「カートから削除」と表示されます。 isPurchased 関数のクリック機能は、上記の状態に応じて items 配列に製品を追加または削除します。 handleButtonClick ページの作成 最後に、チュートリアルの前のセクションで作成したコンポーネントをインポートし、アプリケーションのページ ロジックを作成しましょう。 ホームページ ディレクトリの を開き、その内容を次のように編集します。 app page.tsx import Image from "next/image"; import Header from "./components/Header"; import Text from "./components/Text"; import Footer from "./components/Footer"; import { getPages } from "./services/fetchData"; import { PageAPI } from "./interfaces/data"; export default async function Home() { const pages = await getPages(); const getValues = (el: PageAPI) => { const { herotitle, herodescription, heroimage } = el.attributeValues.en_US; return { title: herotitle.value[0].htmlValue, description: herodescription.value[0].htmlValue, image: heroimage.value[0].downloadLink, }; }; const pageContent = getValues(pages[0]); return ( <div className="flex flex-col min-h-screen"> <Header pages={pages} /> <div className="flex flex-row mx-auto max-w-screen-xl"> <div className="flex-1"> <Text className="text-6xl pb-10 text-gray-900" text={pageContent.title} /> <Text className="text-xl pb-8 text-gray-500 text-justify" text={pageContent.description} /> </div> <div className="flex-1 flex justify-end items-center"> <Image src={pageContent.image} alt="Photo by Karsten Winegeart on Unsplash" width={450} height={900} /> </div> </div> <Footer /> </div> ); } ホーム ページで、まず 関数を呼び出してヘッダーのデータを取得します。 getPages 次に、 関数を使用してヒーロー ページ データを取得し、処理を容易にするためにそれらを オブジェクトに変換します。 getValues pageContent 次に、インポートされたヘッダーとフッターのコンポーネントをレンダリングし、ヒーローのタイトル、説明、画像に必要な値を渡します。 製品ページ ディレクトリに新しいフォルダー を作成し、その中にファイル を作成します。 app [category] page.tsx 特定のファイル名を使用することは、NextJS がルートを処理し、URL パラメーターにアクセスするために使用するものであるため、重要です。 次のコードを に含めます。 page.tsx import Header from "../components/Header"; import Footer from "../components/Footer"; import Card from "../components/Card"; import { getPages, getProducts } from "../services/fetchData"; import { ProductAPI, URLProps } from "../interfaces/data"; export default async function Product({ params }: URLProps) { const { category } = params; const pages = await getPages(); const products = await getProducts(category); const getValues = (products: ProductAPI[]) => { return products.map((el) => { const { producttitle, productsubtitle, productdescription, productimage, productprice, } = el.attributeValues.en_US; return { id: el.id, category: category, title: producttitle.value[0].htmlValue, subtitle: productsubtitle.value[0].htmlValue, description: productdescription.value[0].htmlValue, image: productimage.value[0].downloadLink, price: productprice.value, }; }); }; const productItems = getValues(products.items); return ( <div className="flex flex-col min-h-screen"> <Header pages={pages} /> <div className="mx-auto max-w-screen-xl px-8"> <h2 className="text-4xl text-gray-900 mb-12"> Browse our {category} collection: </h2> <div className="grid gap-x-6 gap-y-10 grid-cols-4 mt-6"> {productItems.map((product) => { return <Card key={product.id} product={product} />; })} </div> </div> <Footer /> </div> ); } 製品ページの場合は、まず URL から パラメーターを取得します。これをさらに 関数に渡し、サイトのどのページにアクセスしたかに基づいて製品のどのカテゴリーを取得する必要があるかを記述します。 category getProducts データを受信したら、処理を容易にするためにページに必要なすべての属性で構成されるオブジェクトの配列 を作成します。 productItems 次に、 メソッドを介してループし、 フォルダーからインポートした Card コンポーネントに props を渡すことで、画面にレンダリングします。 map component プレビューページ フォルダー内に、 という名前の別のフォルダーを作成します。 [category] [productId] 新しく作成したフォルダーを開き、その中に次のコードを含むファイル を作成します。 page.tsx import Header from "../../components/Header"; import Preview from "../../components/Preview"; import AddToCart from "../../components/AddToCart"; import Footer from "../../components/Footer"; import { getPages, getProduct } from "../../services/fetchData"; import { ProductAPI, URLProps } from "../../interfaces/data"; export default async function Product({ params }: URLProps) { const { category, productId } = params; const pages = await getPages(); const product = await getProduct(productId); const getValues = (el: ProductAPI) => { const { producttitle, productsubtitle, productdescription, productimage, productprice, } = el.attributeValues.en_US; return { id: el.id, category: category, title: producttitle.value[0].htmlValue, subtitle: productsubtitle.value[0].htmlValue, description: productdescription.value[0].htmlValue, image: productimage.value[0].downloadLink, price: productprice.value, }; }; const productItem = getValues(product); return ( <div className="flex flex-col min-h-screen"> <Header pages={pages} /> <div className="flex mx-auto max-w-screen-xl"> <div className="flex-1 flex justify-start items-center"> <Preview productItem={productItem}> <AddToCart id={productId} category={category} title={productItem.title} subtitle={productItem.subtitle} description={productItem.description} image={productItem.image} price={productItem.price} /> </Preview> </div> </div> <Footer /> </div> ); } このページでは、ユーザーが製品ページでカードをクリックすると、個々の製品の詳細を表示できます。 まず URL から パラメーターを取得し、さらに 関数に渡して、プレビュー ページで表示される製品に基づいて取得する必要がある製品を指定します。 productId getProduct データを受信したら、Preview コンポーネントに小道具として渡す必要なすべての属性で構成されるオブジェクト を作成します。 productItem また、 パラメーターも取得します。これは、カート ページにアイテムの有効なリンクを作成できるように、「カートに追加」コンポーネントに渡す必要があるためです。 category カートページ 最後に、 ディレクトリに新しいフォルダー を作成します。 app cart それを開き、その中に次のコードを使用して新しいファイル を作成します。 page.tsx import Header from "../components/Header"; import Order from "../components/Order"; import Footer from "../components/Footer"; import { getPages } from "../services/fetchData"; export default async function Cart() { const pages = await getPages(); return ( <div className="flex flex-col min-h-screen"> <Header pages={pages} /> <div className="container mx-auto max-w-screen-xl px-8"> <h2 className="text-4xl text-gray-900 mb-12">Shopping cart summary:</h2> <Order /> </div> <Footer /> </div> ); } まず必要なデータを取得し、それを小道具としてヘッダーに渡します。 次に、ナビゲーションを含む Header コンポーネント、ユーザーがカートに追加したすべてのアイテムをリストする Order コンポーネント、そして会社名と著作権情報を含む Footer コンポーネントをレンダリングしました。 テスト おめでとうございます。機能するプロジェクトが作成されました。 まず、開発者サーバーがまだ実行されているかどうかを確認します。そうでない場合は、 コマンドを実行して再度起動し、 にアクセスして表示します。 npm run dev localhost:3000 プロジェクトは次のようになっているはずです。 ご覧のとおり、ホーム セクションのコンテンツは、データ フィールドで指定したホーム属性セットから正常にフェッチされました。 また、OneEntry CMS カタログのすべてのアイテムは、すべての情報が適切に表示された状態で、衣類およびギアのセクションに取得されています。 NextJS のルート処理と製品パラメーターのおかげで、ユーザーは専用ページで各製品を個別にプレビューすることもできます。 また、すべての機能とイベントは期待どおりに動作し、ユーザーはショッピング カートに商品を追加したり、ショッピング カートから商品を削除したりすることができ、合計が計算されます。 結論 このチュートリアルでは、 のおかげで、ユーザーが Web サイト ページとそのコンテンツを作成、更新、削除できるだけでなく、使いやすいカタログ インターフェイスで製品を簡単に管理できる e コマース プロジェクトを作成しました。 OneEntry CMS コードは で入手できるので、ニーズに合わせて自由に複製して機能を追加してください。さらにメニュー セクションを追加したり、個々のコンポーネントを拡張したり、さらにコンポーネントを追加して新しい機能を実装したりすることもできます。 GitHub これが役に立ち、OneEntry CMS をバックエンド ソリューションとして使用する方法、それをアプリケーションのフロントエンドと組み合わせる方法、NextJS、Typescript、Tailwind の最高の機能を使用する方法についての洞察が得られれば幸いです。 。 を購読して、私が発見した最高のリソース、ツール、生産性のヒント、キャリア成長のヒントを必ず受け取ってください。 ニュースレター また、 、 、 で私とつながってください。 Twitter LinkedIn GitHub でも公開されています ここ