SSR 的全栈电商应用
构建一个带有 服务端渲染 (SSR) 的全栈电商应用需要涉及到前端的 React、Next.js 框架、状态管理工具(如 Redux、MobX、Recoil、Zustand)、后端 API、数据库以及页面 SEO 优化等多个知识点。这个应用将具备商品展示、购物车、用户登录/注册、订单管理等电商核心功能。
下面是一个详细的构建指南,从项目结构、技术选型到具体实现。
项目结构规划
我们使用 Next.js 作为前端框架来处理 SSR,同时通过 Next.js API 路由 实现后端逻辑,数据库使用 MongoDB 进行商品、用户、订单等数据的存储和管理。状态管理可以根据项目的需求选择工具,本文以 Zustand 为例。
1. 项目结构
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 配置技术选型
- 前端框架:Next.js(用于 SSR 和路由处理)
- 后端 API:Next.js API Routes(处理登录、注册、商品、订单等业务逻辑)
- 状态管理:Zustand(轻量状态管理)
- 数据库:MongoDB(使用 Mongoose 进行数据交互)
- 身份验证:JWT(JSON Web Token)处理用户登录、注册
- 样式处理:Tailwind CSS 或 Styled Components
- 支付集成:Stripe 或其他支付平台
详细实现步骤
1. 初始化项目
首先,使用 Next.js 创建项目:
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 用于管理购物车和用户状态。
// /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;// /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 来获取商品数据。
// /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 作为数据库):
// /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:
// /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 模型:
// /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 中展示每个商品的详细信息。
// /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。
// /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 页面展示了用户已添加到购物车的商品,用户可以查看商品列表、删除商品,并跳转到结账页面。
// /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 页面展示结账信息,用户可以选择支付方式(支付宝或微信),并跳转到支付页面。在点击支付按钮时,页面会发送一个请求到后端,后端处理支付宝或微信支付的订单创建,并返回支付链接。
// /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,并且使用后端来生成支付订单。
// /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' });
}
}支付集成概述
前端 (Checkout Page):
- 用户选择支付方式(支付宝或微信支付)。
- 点击“支付”按钮时,前端发送一个请求到后端,后端创建支付订单并返回支付链接。
后端 (API Route):
- 根据支付方式调用相应的支付 API(支付宝或微信支付)。
- 返回支付链接,用户跳转到第三方支付页面进行支付。
集成支付平台
- 支付宝支付:你可以通过支付宝官方的 Alipay API 进行支付集成,支付宝会提供支付 SDK 和 API。
- 微信支付:同样,你可以通过 微信支付 API 来集成微信支付服务。
8. SEO 优化
- 使用 Next.js 的
Head组件为每个页面设置适当的标题、描述和关键字,提升 SEO 排名。
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 优化。应用中的关键功能包括商品展示、购物车、用户认证、结账和订单管理等。