MK
摩柯社区 - 一个极简的技术知识社区
AI 面试

Next.js动态路由在电商网站中的应用分析

2022-01-182.5k 阅读

Next.js动态路由基础概念

动态路由定义与原理

在Web开发中,动态路由是指根据不同的参数生成不同的路由路径。例如,在电商网站中,每个商品都有独特的详情页,若为每个商品都手动配置一个固定路由显然不现实。动态路由则允许通过变量来表示这些不同的商品ID等信息,实现灵活的页面渲染。

Next.js的动态路由基于文件系统,在pages目录下,通过在文件名中使用方括号[]来定义动态路由参数。比如,创建一个名为pages/products/[productId].js的文件,productId就是动态路由参数。当用户访问/products/123这样的URL时,123会作为productId传递给[productId].js页面组件。

其原理在于Next.js的路由系统会解析URL,根据文件系统中的路由定义,匹配对应的页面组件,并将动态参数传递进去。这种基于文件系统的路由方式,使得代码结构清晰,易于维护和扩展。

动态路由匹配规则

  1. 简单参数匹配:如上述[productId].js示例,只要URL路径中products后跟着一个值,就会匹配该动态路由文件。例如/products/abc/products/123等都会触发[productId].js页面的渲染,abc123会作为productId的值。
  2. 嵌套动态路由:Next.js支持嵌套动态路由。比如在电商网站中,可能有分类下的商品详情页。可以创建pages/categories/[categoryId]/products/[productId].js这样的文件结构。当访问/categories/1/products/123时,1会作为categoryId123会作为productId传递给相应页面组件。
  3. 可选参数:可以通过在方括号后添加问号?来表示可选参数。例如pages/posts/[postId]?[commentId].js,访问/posts/1时,postId1commentId不存在;访问/posts/1/2时,postId1commentId2

Next.js动态路由在电商商品展示中的应用

商品列表页与动态路由

  1. 创建商品列表页 在电商网站中,商品列表页展示众多商品的简要信息,点击每个商品会跳转到其详情页。假设我们有一个products目录在pages下,创建pages/products/index.js作为商品列表页。
import React from 'react';
import Link from 'next/link';
// 模拟从API获取商品数据
const products = [
  { id: 1, name: '商品1' },
  { id: 2, name: '商品2' }
];

const ProductList = () => {
  return (
    <div>
      <h1>商品列表</h1>
      {products.map(product => (
        <Link href={`/products/${product.id}`} key={product.id}>
          <a>{product.name}</a>
        </Link>
      ))}
    </div>
  );
};

export default ProductList;

这里使用next/link组件来创建链接,href属性动态生成指向商品详情页的URL,格式为/products/${product.id}

  1. 商品详情页动态路由 创建pages/products/[productId].js作为商品详情页。
import React from'react';
import { useRouter } from 'next/router';

const ProductDetail = () => {
  const router = useRouter();
  const { productId } = router.query;
  // 模拟根据productId从API获取商品详细数据
  const product = { id: productId, name: `商品${productId}` };

  return (
    <div>
      <h1>{product.name}</h1>
      <p>商品ID: {product.id}</p>
    </div>
  );
};

export default ProductDetail;

通过useRouter钩子函数获取路由参数,router.query包含了动态路由参数,这里提取出productId,并根据productId模拟获取商品详细数据进行展示。

多属性商品筛选与动态路由

  1. 筛选条件传递 在电商网站中,用户常常通过多种属性筛选商品,如价格范围、品牌等。假设我们有价格筛选功能,希望将筛选条件体现在URL中。可以创建pages/products/filter/[priceRange].js这样的动态路由页面。
import React from'react';
import { useRouter } from 'next/router';

const ProductFilter = () => {
  const router = useRouter();
  const { priceRange } = router.query;
  // 模拟根据价格范围从API获取商品数据
  const filteredProducts = [];
  if (priceRange === 'low') {
    // 从API获取低价商品数据
  } else if (priceRange === 'high') {
    // 从API获取高价商品数据
  }

  return (
    <div>
      <h1>筛选后的商品</h1>
      {filteredProducts.map(product => (
        <div key={product.id}>{product.name}</div>
      ))}
    </div>
  );
};

export default ProductFilter;

在商品列表页,可以通过next/link组件传递筛选条件到该动态路由页面。

import React from'react';
import Link from 'next/link';

const ProductList = () => {
  return (
    <div>
      <h1>商品列表</h1>
      <Link href="/products/filter/low">
        <a>查看低价商品</a>
      </Link>
      <Link href="/products/filter/high">
        <a>查看高价商品</a>
      </Link>
    </div>
  );
};

export default ProductList;
  1. 多条件组合 实际应用中,可能需要多个筛选条件组合,如品牌和价格范围。可以扩展动态路由为pages/products/filter/[brand]/[priceRange].js
import React from'react';
import { useRouter } from 'next/router';

const ProductFilter = () => {
  const router = useRouter();
  const { brand, priceRange } = router.query;
  // 模拟根据品牌和价格范围从API获取商品数据
  const filteredProducts = [];
  if (brand === 'brand1' && priceRange === 'low') {
    // 从API获取品牌1的低价商品数据
  }

  return (
    <div>
      <h1>筛选后的商品</h1>
      {filteredProducts.map(product => (
        <div key={product.id}>{product.name}</div>
      ))}
    </div>
  );
};

export default ProductFilter;

在商品列表页传递多个筛选条件:

import React from'react';
import Link from 'next/link';

const ProductList = () => {
  return (
    <div>
      <h1>商品列表</h1>
      <Link href="/products/filter/brand1/low">
        <a>查看品牌1的低价商品</a>
      </Link>
    </div>
  );
};

export default ProductList;

Next.js动态路由在电商购物流程中的应用

购物车页面动态路由

  1. 购物车ID与动态路由 在电商网站中,每个用户的购物车可以用一个唯一的ID标识。创建pages/cart/[cartId].js作为购物车页面。
import React from'react';
import { useRouter } from 'next/router';

const Cart = () => {
  const router = useRouter();
  const { cartId } = router.query;
  // 模拟根据cartId从API获取购物车数据
  const cartItems = [];
  // 假设从API获取购物车商品列表

  return (
    <div>
      <h1>购物车 {cartId}</h1>
      {cartItems.map(item => (
        <div key={item.id}>
          <p>{item.name}</p>
          <p>数量: {item.quantity}</p>
        </div>
      ))}
    </div>
  );
};

export default Cart;

在用户登录后,系统可以为其分配一个购物车ID,并通过next/link组件跳转到对应的购物车页面。例如,在用户个人中心页面:

import React from'react';
import Link from 'next/link';

const UserDashboard = () => {
  const cartId = '12345'; // 假设从用户信息中获取购物车ID
  return (
    <div>
      <h1>用户中心</h1>
      <Link href={`/cart/${cartId}`}>
        <a>查看购物车</a>
      </Link>
    </div>
  );
};

export default UserDashboard;
  1. 购物车操作与动态路由更新 当用户在购物车中添加、删除商品时,购物车数据会发生变化,同时URL也需要更新以反映这些变化。例如,当用户添加商品到购物车时,购物车ID不变,但购物车版本号可能改变。可以在购物车操作函数中更新路由。
import React from'react';
import { useRouter } from 'next/router';

const Cart = () => {
  const router = useRouter();
  const { cartId } = router.query;
  const [cartVersion, setCartVersion] = React.useState(1);
  const addItemToCart = () => {
    // 执行添加商品到购物车的逻辑
    setCartVersion(cartVersion + 1);
    router.push(`/cart/${cartId}?version=${cartVersion}`);
  };

  return (
    <div>
      <h1>购物车 {cartId}</h1>
      <button onClick={addItemToCart}>添加商品</button>
    </div>
  );
};

export default Cart;

这里通过router.push方法更新URL,将购物车版本号作为查询参数添加到URL中,以便前端和后端都能识别购物车的变化。

订单确认与支付页面动态路由

  1. 订单ID与动态路由 在用户完成购物车操作后,进入订单确认页面。每个订单有唯一的订单ID,创建pages/order/[orderId]/confirm.js作为订单确认页面。
import React from'react';
import { useRouter } from 'next/router';

const OrderConfirm = () => {
  const router = useRouter();
  const { orderId } = router.query;
  // 模拟根据orderId从API获取订单数据
  const order = { id: orderId, items: [] };
  // 假设从API获取订单商品列表等数据

  return (
    <div>
      <h1>订单确认 {orderId}</h1>
      {order.items.map(item => (
        <div key={item.id}>
          <p>{item.name}</p>
          <p>数量: {item.quantity}</p>
        </div>
      ))}
    </div>
  );
};

export default OrderConfirm;

在购物车页面,当用户点击“去结算”按钮时,生成订单ID并跳转到订单确认页面。

import React from'react';
import Link from 'next/link';

const Cart = () => {
  const createOrder = () => {
    const orderId = '67890'; // 假设生成订单ID
    return orderId;
  };
  const orderId = createOrder();
  return (
    <div>
      <h1>购物车</h1>
      <Link href={`/order/${orderId}/confirm`}>
        <a>去结算</a>
      </Link>
    </div>
  );
};

export default Cart;
  1. 支付状态与动态路由更新 在订单确认页面,用户完成支付后,支付状态需要反映在URL中。创建pages/order/[orderId]/payment/[paymentStatus].js作为支付结果页面。
import React from'react';
import { useRouter } from 'next/router';

const PaymentResult = () => {
  const router = useRouter();
  const { orderId, paymentStatus } = router.query;
  return (
    <div>
      <h1>支付结果 {orderId}</h1>
      <p>支付状态: {paymentStatus}</p>
    </div>
  );
};

export default PaymentResult;

在支付完成后,根据支付结果更新路由。例如,支付成功时:

import React from'react';
import { useRouter } from 'next/router';

const OrderConfirm = () => {
  const router = useRouter();
  const { orderId } = router.query;
  const handlePaymentSuccess = () => {
    router.push(`/order/${orderId}/payment/success`);
  };

  return (
    <div>
      <h1>订单确认 {orderId}</h1>
      <button onClick={handlePaymentSuccess}>支付成功</button>
    </div>
  );
};

export default OrderConfirm;

支付失败时同理,更新为/order/${orderId}/payment/failure

Next.js动态路由与SEO优化

动态路由页面SEO基础

  1. 页面标题与元描述 对于电商网站的动态路由页面,如商品详情页,设置合适的页面标题和元描述对SEO至关重要。在pages/products/[productId].js页面中,可以使用next/head组件来设置。
import React from'react';
import { useRouter } from 'next/router';
import Head from 'next/head';

const ProductDetail = () => {
  const router = useRouter();
  const { productId } = router.query;
  const product = { id: productId, name: `商品${productId}` };

  return (
    <div>
      <Head>
        <title>{product.name} - 电商平台</title>
        <meta name="description" content={`${product.name}的详细信息,在本电商平台购买。`} />
      </Head>
      <h1>{product.name}</h1>
      <p>商品ID: {product.id}</p>
    </div>
  );
};

export default ProductDetail;

这里根据商品名称动态设置页面标题和元描述,使得搜索引擎能更好地理解页面内容。

  1. URL结构优化 Next.js的动态路由天然支持创建友好的URL结构。对于商品详情页,使用类似/products/商品名称-123这样的URL比/products/123更有利于SEO。可以通过在获取商品数据时,将商品名称进行处理(如去除特殊字符、转换为小写等)并拼接到URL中。
import React from'react';
import Link from 'next/link';
// 模拟从API获取商品数据
const products = [
  { id: 1, name: '商品1' },
  { id: 2, name: '商品2' }
];

const ProductList = () => {
  return (
    <div>
      <h1>商品列表</h1>
      {products.map(product => {
        const slug = product.name.toLowerCase().replace(/\W+/g, '-');
        return (
          <Link href={`/products/${slug}-${product.id}`} key={product.id}>
            <a>{product.name}</a>
          </Link>
        );
      })}
    </div>
  );
};

export default ProductList;

pages/products/[slug]-[productId].js页面中:

import React from'react';
import { useRouter } from 'next/router';
import Head from 'next/head';

const ProductDetail = () => {
  const router = useRouter();
  const { slug, productId } = router.query;
  const product = { id: productId, name: slug.replace(/-/g,'') };

  return (
    <div>
      <Head>
        <title>{product.name} - 电商平台</title>
        <meta name="description" content={`${product.name}的详细信息,在本电商平台购买。`} />
      </Head>
      <h1>{product.name}</h1>
      <p>商品ID: {product.id}</p>
    </div>
  );
};

export default ProductDetail;

动态路由页面SEO高级技巧

  1. 预渲染与静态化 Next.js支持静态生成(SSG)和服务器端渲染(SSR)。对于电商商品详情页,可以使用静态生成,在构建时生成HTML页面,提高搜索引擎抓取效率。在pages/products/[productId].js中,可以使用getStaticProps函数。
import React from'react';
import { useRouter } from 'next/router';
import Head from 'next/head';

const ProductDetail = ({ product }) => {
  return (
    <div>
      <Head>
        <title>{product.name} - 电商平台</title>
        <meta name="description" content={`${product.name}的详细信息,在本电商平台购买。`} />
      </Head>
      <h1>{product.name}</h1>
      <p>商品ID: {product.id}</p>
    </div>
  );
};

export async function getStaticProps(context) {
  const { productId } = context.params;
  // 从API获取商品数据
  const product = { id: productId, name: `商品${productId}` };
  return {
    props: {
      product
    },
    revalidate: 60 * 60 * 24 // 一天后重新验证
  };
}

export async function getStaticPaths() {
  // 假设从API获取所有商品ID
  const productIds = [1, 2];
  const paths = productIds.map(productId => ({
    params: { productId: productId.toString() }
  }));
  return { paths, fallback: false };
}

export default ProductDetail;

getStaticPaths函数定义了所有可能的动态路由路径,getStaticProps函数在构建时获取数据并传递给组件。revalidate参数用于设置页面重新验证的时间间隔,对于变化不频繁的商品数据,这种方式能有效提高SEO性能。

  1. 结构化数据标记 结构化数据标记(如JSON - LD格式)可以帮助搜索引擎更好地理解页面内容。在商品详情页,可以添加商品相关的结构化数据。
import React from'react';
import { useRouter } from 'next/router';
import Head from 'next/head';

const ProductDetail = ({ product }) => {
  return (
    <div>
      <Head>
        <title>{product.name} - 电商平台</title>
        <meta name="description" content={`${product.name}的详细信息,在本电商平台购买。`} />
        <script type="application/ld+json">
          {JSON.stringify({
            "@context": "https://schema.org",
            "@type": "Product",
            "name": product.name,
            "description": `商品${product.id}的详细描述`,
            "sku": product.id,
            "offers": {
              "@type": "Offer",
              "price": 100, // 假设价格
              "priceCurrency": "CNY"
            }
          })}
        </script>
      </Head>
      <h1>{product.name}</h1>
      <p>商品ID: {product.id}</p>
    </div>
  );
};

export async function getStaticProps(context) {
  const { productId } = context.params;
  // 从API获取商品数据
  const product = { id: productId, name: `商品${productId}` };
  return {
    props: {
      product
    },
    revalidate: 60 * 60 * 24
  };
}

export async function getStaticPaths() {
  const productIds = [1, 2];
  const paths = productIds.map(productId => ({
    params: { productId: productId.toString() }
  }));
  return { paths, fallback: false };
}

export default ProductDetail;

这样,搜索引擎在抓取页面时,能更准确地展示商品信息,如在搜索结果中显示价格、商品名称等关键信息,提高页面的吸引力和点击率。

Next.js动态路由性能优化

动态路由页面加载性能

  1. 代码分割与懒加载 Next.js会自动进行代码分割,对于动态路由页面,只有在需要时才加载相应的代码。例如,在商品详情页pages/products/[productId].js,如果页面中有一些不常用的功能模块,如商品评论的富文本编辑器,可以使用动态导入进行懒加载。
import React, { lazy, Suspense } from'react';
import { useRouter } from 'next/router';

const CommentEditor = lazy(() => import('./CommentEditor'));

const ProductDetail = () => {
  const router = useRouter();
  const { productId } = router.query;
  const showCommentEditor = true; // 假设根据某些条件决定是否显示

  return (
    <div>
      <h1>商品详情 {productId}</h1>
      {showCommentEditor && (
        <Suspense fallback={<div>加载中...</div>}>
          <CommentEditor />
        </Suspense>
      )}
    </div>
  );
};

export default ProductDetail;

这里CommentEditor组件通过lazy函数进行动态导入,Suspense组件用于在组件加载时显示加载提示,提高用户体验。

  1. 缓存策略 对于电商网站中动态路由页面的数据,合理设置缓存策略可以减少重复请求,提高加载性能。在getStaticProps函数中,可以设置缓存头。
import React from'react';
import { useRouter } from 'next/router';
import Head from 'next/head';

const ProductDetail = ({ product }) => {
  return (
    <div>
      <Head>
        <title>{product.name} - 电商平台</title>
        <meta name="description" content={`${product.name}的详细信息,在本电商平台购买。`} />
      </Head>
      <h1>{product.name}</h1>
      <p>商品ID: {product.id}</p>
    </div>
  );
};

export async function getStaticProps(context) {
  const { productId } = context.params;
  // 从API获取商品数据
  const product = { id: productId, name: `商品${productId}` };
  return {
    props: {
      product
    },
    revalidate: 60 * 60 * 24,
    headers: {
      'Cache - Control':'s - maxage = 86400, stale - while - revalidate'
    }
  };
}

export async function getStaticPaths() {
  const productIds = [1, 2];
  const paths = productIds.map(productId => ({
    params: { productId: productId.toString() }
  }));
  return { paths, fallback: false };
}

export default ProductDetail;

这里设置了Cache - Control头,s - maxage = 86400表示缓存有效期为一天,stale - while - revalidate表示在缓存过期后,仍可以使用缓存内容,同时后台重新验证数据,减少用户等待时间。

动态路由页面交互性能

  1. 客户端路由与过渡动画 Next.js的客户端路由可以实现页面之间的平滑过渡,提高交互性能。在商品列表页和商品详情页之间切换时,可以添加过渡动画。首先,安装next - transition库。
npm install next - transition

pages/products/index.js商品列表页:

import React from'react';
import Link from 'next/link';
import { Transition } from 'next - transition';

const ProductList = () => {
  return (
    <div>
      <h1>商品列表</h1>
      <Transition
        from={{ opacity: 0 }}
        enter={{ opacity: 1 }}
        leave={{ opacity: 0 }}
        duration={300}
      >
        {products.map(product => (
          <Link href={`/products/${product.id}`} key={product.id}>
            <a>{product.name}</a>
          </Link>
        ))}
      </Transition>
    </div>
  );
};

export default ProductList;

pages/products/[productId].js商品详情页:

import React from'react';
import { useRouter } from 'next/router';
import { Transition } from 'next - transition';

const ProductDetail = () => {
  const router = useRouter();
  const { productId } = router.query;

  return (
    <div>
      <Transition
        from={{ opacity: 0 }}
        enter={{ opacity: 1 }}
        leave={{ opacity: 0 }}
        duration={300}
      >
        <h1>商品详情 {productId}</h1>
      </Transition>
    </div>
  );
};

export default ProductDetail;

这样,在页面切换时会有淡入淡出的过渡动画,使交互更加流畅。

  1. 事件委托与性能优化 在电商网站的动态路由页面中,如商品列表页可能有大量商品,每个商品可能有点击、鼠标悬停等事件。如果为每个商品元素都绑定事件,会增加内存开销,影响性能。可以使用事件委托,将事件绑定在父元素上。
import React, { useRef } from'react';
import Link from 'next/link';

const ProductList = () => {
  const productListRef = useRef(null);
  const handleClick = (event) => {
    if (event.target.tagName === 'A') {
      // 处理商品点击逻辑
    }
  };
  React.useEffect(() => {
    const list = productListRef.current;
    if (list) {
      list.addEventListener('click', handleClick);
    }
    return () => {
      if (list) {
        list.removeEventListener('click', handleClick);
      }
    };
  }, []);

  return (
    <div ref={productListRef}>
      <h1>商品列表</h1>
      {products.map(product => (
        <Link href={`/products/${product.id}`} key={product.id}>
          <a>{product.name}</a>
        </Link>
      ))}
    </div>
  );
};

export default ProductList;

这里将点击事件绑定在商品列表的父元素上,通过判断事件目标来处理具体商品的点击逻辑,减少了事件绑定的数量,提高了页面性能。