← 目录从零开始学习React

SSR 的全栈电商应用

2024/9/22

构建一个带有 服务端渲染 (SSR) 的全栈电商应用需要涉及到前端的 React、Next.js 框架、状态管理工具(如 Redux、MobX、Recoil、Zustand)、后端 API、数据库以及页面 SEO 优化等多个知识点。这个应用将具备商品展示、购物车、用户登录/注册、订单管理等电商核心功能。

下面是一个详细的构建指南,从项目结构、技术选型到具体实现。


项目结构规划

我们使用 Next.js 作为前端框架来处理 SSR,同时通过 Next.js API 路由 实现后端逻辑,数据库使用 MongoDB 进行商品、用户、订单等数据的存储和管理。状态管理可以根据项目的需求选择工具,本文以 Zustand 为例。

1. 项目结构

bash
ecommerce-app/
├── components/          # 复用的组件(如导航栏、商品卡片等)
├── pages/               # Next.js 页面和路由
│   ├── api/             # Next.js 后端 API 路由
│   ├── index.js         # 首页
│   ├── product/[id].js  # 商品详情页
│   ├── cart.js          # 购物车页面
│   └── checkout.js      # 结账页面
├── store/               # 状态管理(如 Zustand)
├── utils/               # 工具函数
├── public/              # 静态资源(如图片、图标等)
├── styles/              # 样式文件
├── .env                 # 环境变量
├── package.json         # 项目依赖
└── next.config.js       # Next.js 配置

技术选型

  1. 前端框架:Next.js(用于 SSR 和路由处理)
  2. 后端 API:Next.js API Routes(处理登录、注册、商品、订单等业务逻辑)
  3. 状态管理:Zustand(轻量状态管理)
  4. 数据库:MongoDB(使用 Mongoose 进行数据交互)
  5. 身份验证:JWT(JSON Web Token)处理用户登录、注册
  6. 样式处理:Tailwind CSS 或 Styled Components
  7. 支付集成:Stripe 或其他支付平台

详细实现步骤

1. 初始化项目

首先,使用 Next.js 创建项目:

bash
npx create-next-app ecommerce-app
cd ecommerce-app
npm install zustand mongoose jsonwebtoken bcryptjs
  • Zustand:用于状态管理。
  • Mongoose:用于 MongoDB 数据交互。
  • jsonwebtoken 和 bcryptjs:用于用户认证和密码加密。

2. 设计状态管理 (Zustand)

在 /store 文件夹中创建 cartStore.js 和 userStore.js 用于管理购物车和用户状态。

javascript
// /store/cartStore.js
import create from 'zustand';

const useCartStore = create((set) => ({
  cart: [],
  addToCart: (product) =>
    set((state) => ({
      cart: [...state.cart, product],
    })),
  removeFromCart: (id) =>
    set((state) => ({
      cart: state.cart.filter((item) => item.id !== id),
    })),
}));

export default useCartStore;
javascript
// /store/userStore.js
import create from 'zustand';

const useUserStore = create((set) => ({
  user: null,
  setUser: (userData) => set({ user: userData }),
  logout: () => set({ user: null }),
}));

export default useUserStore;

3. 构建商品展示页面

在 pages/index.js 中,展示商品列表并使用 SSR 来获取商品数据。

javascript
// /pages/index.js
import React from 'react';
import Link from 'next/link';

export default function Home({ products }) {
  return (
    <div>
      <h1>欢迎来到电商平台</h1>
      <div className="product-list">
        {products.map((product) => (
          <div key={product._id} className="product-item">
            <Link href={`/product/${product._id}`}>
              <a>
                <h2>{product.name}</h2>
                <p>Price: ${product.price}</p>
              </a>
            </Link>
          </div>
        ))}
      </div>
    </div>
  );
}

// SSR: 获取所有商品数据
export async function getServerSideProps() {
  const res = await fetch(`${process.env.NEXT_PUBLIC_API_URL}/api/products`);
  const products = await res.json();

  return {
    props: { products }, // 传递到组件
  };
}

4. 实现后端 API 路由

在 pages/api/products.js 中编写 API 逻辑来获取商品列表(使用 MongoDB 作为数据库):

javascript
// /pages/api/products.js
import dbConnect from '../../utils/dbConnect';
import Product from '../../models/Product';

export default async function handler(req, res) {
  await dbConnect();

  if (req.method === 'GET') {
    const products = await Product.find({});
    res.status(200).json(products);
  } else {
    res.status(400).json({ error: 'This method is not supported' });
  }
}
  • dbConnect.js 用于连接 MongoDB:
javascript
// /utils/dbConnect.js
import mongoose from 'mongoose';

const MONGODB_URI = process.env.MONGODB_URI;

if (!MONGODB_URI) {
  throw new Error('Please define the MONGODB_URI environment variable');
}

let cached = global.mongoose;

if (!cached) {
  cached = global.mongoose = { conn: null, promise: null };
}

async function dbConnect() {
  if (cached.conn) {
    return cached.conn;
  }

  if (!cached.promise) {
    cached.promise = mongoose.connect(MONGODB_URI).then((mongoose) => {
      return mongoose;
    });
  }

  cached.conn = await cached.promise;
  return cached.conn;
}

export default dbConnect;
  • Product 模型:
javascript
// /models/Product.js
import mongoose from 'mongoose';

const ProductSchema = new mongoose.Schema({
  name: { type: String, required: true },
  price: { type: Number, required: true },
  description: { type: String, required: true },
});

export default mongoose.models.Product || mongoose.model('Product', ProductSchema);

5. 构建商品详情页

在 pages/product/[id].js 中展示每个商品的详细信息。

javascript
// /pages/product/[id].js
import { useRouter } from 'next/router';
import useCartStore from '../../store/cartStore';

export default function ProductPage({ product }) {
  const addToCart = useCartStore((state) => state.addToCart);

  return (
    <div>
      <h1>{product.name}</h1>
      <p>{product.description}</p>
      <p>Price: ${product.price}</p>
      <button onClick={() => addToCart(product)}>Add to Cart</button>
    </div>
  );
}

export async function getServerSideProps(context) {
  const { id } = context.params;
  const res = await fetch(`${process.env.NEXT_PUBLIC_API_URL}/api/products/${id}`);
  const product = await res.json();

  return {
    props: { product },
  };
}

6. 用户登录和身份验证

在 pages/api/auth/login.js 中处理用户登录逻辑,并生成 JWT。

javascript
// /pages/api/auth/login.js
import dbConnect from '../../../utils/dbConnect';
import User from '../../../models/User';
import bcrypt from 'bcryptjs';
import jwt from 'jsonwebtoken';

export default async function handler(req, res) {
  await dbConnect();

  const { email, password } = req.body;

  const user = await User.findOne({ email });
  if (!user) return res.status(400).json({ message: 'User not found' });

  const isMatch = await bcrypt.compare(password, user.password);
  if (!isMatch) return res.status(400).json({ message: 'Invalid credentials' });

  const token = jwt.sign({ id: user._id }, process.env.JWT_SECRET, { expiresIn: '1h' });

  res.status(200).json({ token });
}
  • 使用 JWT 来验证用户请求的合法性,例如访问订单页面或创建新订单时,需要用户先登录。

7. 购物车和结账功能

在 pages/cart.js 和 pages/checkout.js 中分别实现购物车查看和结账逻辑。用户可以将商品添加到购物车,在结账页面输入支付信息,完成支付后生成订单。

下面是 pages/cart.js 和 pages/checkout.js 的代码实现,结合了购物车的状态管理和支付功能。支付部分集成了 支付宝 和 微信支付,通过支付 API 来完成交易。在真实项目中,支付通常涉及到后端服务器与支付服务的集成,因此前端代码会通过后端 API 来处理支付请求。

为了简化,我们假设你已经在后端集成了支付宝和微信支付的 SDK,前端通过发起支付请求来跳转到相应的支付页面。


1). Cart Page (pages/cart.js)

cart.js 页面展示了用户已添加到购物车的商品,用户可以查看商品列表、删除商品,并跳转到结账页面。

javascript
// /pages/cart.js
import React from 'react';
import Link from 'next/link';
import useCartStore from '../store/cartStore';

export default function Cart() {
  const { cart, removeFromCart } = useCartStore();

  // 计算总价
  const totalPrice = cart.reduce((total, item) => total + item.price, 0);

  return (
    <div className="cart-page">
      <h1>Your Cart</h1>
      {cart.length === 0 ? (
        <p>Your cart is empty.</p>
      ) : (
        <>
          <ul>
            {cart.map((item, index) => (
              <li key={index} className="cart-item">
                <h2>{item.name}</h2>
                <p>Price: ${item.price}</p>
                <button onClick={() => removeFromCart(item.id)}>Remove</button>
              </li>
            ))}
          </ul>
          <h2>Total: ${totalPrice}</h2>
          <Link href="/checkout">
            <button>Proceed to Checkout</button>
          </Link>
        </>
      )}
    </div>
  );
}

2). Checkout Page (pages/checkout.js)

checkout.js 页面展示结账信息,用户可以选择支付方式(支付宝或微信),并跳转到支付页面。在点击支付按钮时,页面会发送一个请求到后端,后端处理支付宝或微信支付的订单创建,并返回支付链接。

javascript
// /pages/checkout.js
import React, { useState } from 'react';
import useCartStore from '../store/cartStore';
import axios from 'axios';
import { useRouter } from 'next/router';

export default function Checkout() {
  const { cart } = useCartStore();
  const [paymentMethod, setPaymentMethod] = useState('');
  const [loading, setLoading] = useState(false);
  const router = useRouter();

  // 计算总价
  const totalPrice = cart.reduce((total, item) => total + item.price, 0);

  // 发起支付请求
  const handlePayment = async () => {
    if (!paymentMethod) {
      alert('Please select a payment method');
      return;
    }

    setLoading(true);

    try {
      const response = await axios.post('/api/pay', {
        cart,
        totalPrice,
        paymentMethod,
      });

      if (response.data.paymentUrl) {
        // 跳转到支付页面
        window.location.href = response.data.paymentUrl;
      }
    } catch (error) {
      console.error('Payment error:', error);
      alert('Payment failed, please try again.');
    } finally {
      setLoading(false);
    }
  };

  return (
    <div className="checkout-page">
      <h1>Checkout</h1>
      {cart.length === 0 ? (
        <p>Your cart is empty.</p>
      ) : (
        <>
          <h2>Total: ${totalPrice}</h2>

          {/* 支付方式选择 */}
          <div>
            <h3>Select Payment Method</h3>
            <label>
              <input
                type="radio"
                name="paymentMethod"
                value="alipay"
                checked={paymentMethod === 'alipay'}
                onChange={() => setPaymentMethod('alipay')}
              />
              支付宝
            </label>
            <label>
              <input
                type="radio"
                name="paymentMethod"
                value="wechat"
                checked={paymentMethod === 'wechat'}
                onChange={() => setPaymentMethod('wechat')}
              />
              微信支付
            </label>
          </div>

          {/* 支付按钮 */}
          <button onClick={handlePayment} disabled={loading}>
            {loading ? 'Processing...' : 'Pay Now'}
          </button>
        </>
      )}
    </div>
  );
}

3). 支付 API 路由 (pages/api/pay.js)

后端支付 API 负责与支付宝和微信支付服务器通信,创建支付订单,并返回支付链接。以下是一个示例,其中假设你已经配置了支付宝和微信支付的 SDK,并且使用后端来生成支付订单。

javascript
// /pages/api/pay.js
import axios from 'axios';

export default async function handler(req, res) {
  const { cart, totalPrice, paymentMethod } = req.body;

  if (!cart || cart.length === 0) {
    return res.status(400).json({ message: 'Cart is empty' });
  }

  // 根据支付方式生成不同的支付订单
  let paymentUrl = '';

  try {
    if (paymentMethod === 'alipay') {
      // 调用支付宝 API 生成支付订单
      const alipayResponse = await axios.post('https://api.alipay.com/pay', {
        amount: totalPrice,
        // 其他支付宝订单参数
      });
      paymentUrl = alipayResponse.data.paymentUrl;
    } else if (paymentMethod === 'wechat') {
      // 调用微信支付 API 生成支付订单
      const wechatResponse = await axios.post('https://api.wechat.com/pay', {
        amount: totalPrice,
        // 其他微信支付订单参数
      });
      paymentUrl = wechatResponse.data.paymentUrl;
    }

    // 返回支付页面 URL
    res.status(200).json({ paymentUrl });
  } catch (error) {
    console.error('Payment error:', error);
    res.status(500).json({ message: 'Payment failed' });
  }
}

支付集成概述
  1. 前端 (Checkout Page):

    • 用户选择支付方式(支付宝或微信支付)。
    • 点击“支付”按钮时,前端发送一个请求到后端,后端创建支付订单并返回支付链接。
  2. 后端 (API Route):

    • 根据支付方式调用相应的支付 API(支付宝或微信支付)。
    • 返回支付链接,用户跳转到第三方支付页面进行支付。

集成支付平台
  • 支付宝支付:你可以通过支付宝官方的 Alipay API 进行支付集成,支付宝会提供支付 SDK 和 API。
  • 微信支付:同样,你可以通过 微信支付 API 来集成微信支付服务。

8. SEO 优化

  • 使用 Next.js 的 Head 组件为每个页面设置适当的标题、描述和关键字,提升 SEO 排名。
jsx
import Head from 'next/head';

export default function ProductPage({ product }) {
  return (
    <>
      <Head>
        <title>{product.name} - 电商平台</title

>
        <meta name="description" content={product.description} />
      </Head>
      <div>
        {/* 商品详细信息 */}
      </div>
    </>
  );
}

总结

这个全栈电商应用示例涵盖了从前端 React 和 Next.js 到后端 API 路由和 MongoDB 数据库的完整技术栈,支持服务端渲染(SSR)和 SEO 优化。应用中的关键功能包括商品展示、购物车、用户认证、结账和订单管理等。