增加按需ISR增量静态更新功能

This commit is contained in:
jingrow 2025-06-24 00:16:18 +08:00
parent a15a7e0c18
commit 58e1794c9f
9 changed files with 81 additions and 21 deletions

View File

@ -12,13 +12,20 @@ const LoadingSpinner = () => (
</div>
);
export const revalidate = Number(process.env.JSITE_REVALIDATE_SECONDS) || 3600;
// Using a static value to comply with Next.js 15 build requirements.
// On-demand revalidation will be handled via the API route.
export const revalidate = 3600;
export async function generateStaticParams() {
const slugs = await getAllSlugs();
return slugs.map(slug => ({
slug: slug,
}));
try {
const slugs = await getAllSlugs();
return slugs.map(slug => ({
slug: slug,
}));
} catch (error) {
console.error("Failed to generate static params:", error);
return [];
}
}
export async function generateMetadata({ params }) {

View File

@ -0,0 +1,55 @@
import { revalidatePath } from 'next/cache';
import { NextRequest, NextResponse } from 'next/server';
/**
* 按需重新验证 API 路由
* 当后端内容更新时通过调用此 API 来手动触发特定页面的重新生成
*
* 调用方式:
* POST /api/revalidate
* Headers:
* Content-Type: application/json
* x-revalidate-secret: YOUR_SECRET_TOKEN
* Body:
* {
* "path": "/page-to-revalidate"
* }
*/
export async function POST(request) {
// 1. 从请求头中获取密钥
const secret = request.headers.get('x-revalidate-secret');
// 2. 验证密钥
// 为了安全,这个密钥应该存储在环境变量中,并且不能硬编码。
if (secret !== process.env.REVALIDATE_TOKEN) {
console.warn('Invalid revalidate token received.');
return NextResponse.json({ message: 'Invalid token' }, { status: 401 });
}
// 3. 从请求体中获取并记录需要重新验证的路径
const body = await request.json();
const { path } = body;
console.log('Revalidation request body:', body);
if (!path) {
console.warn('Revalidation request received without a path.');
return NextResponse.json({ message: 'Path is required' }, { status: 400 });
}
try {
// 4. 调用 Next.js 的 revalidatePath 函数
// 这将清除指定路径的缓存,并在下次请求时重新生成页面。
revalidatePath(path);
console.log(`Successfully revalidated path: ${path}`);
return NextResponse.json({ revalidated: true, now: Date.now() });
} catch (error) {
// 5. 如果发生错误,返回错误信息
console.error(`Error revalidating path: ${path}`, error);
return NextResponse.json(
{ message: 'Error revalidating', error: error.message },
{ status: 500 }
);
}
}

View File

@ -4,7 +4,9 @@ import CategoryItems from "@/components/homes/home-15/CategoryItems";
import React from "react";
import { getSiteSettings } from "@/utils/siteSettings";
export const revalidate = Number(process.env.JSITE_REVALIDATE_SECONDS) || 3600;
// Using a static value to comply with Next.js 15 build requirements.
// On-demand revalidation will be handled via the API route.
export const revalidate = 3600;
export async function generateMetadata() {
const siteUrl = process.env.SITE_URL;

View File

@ -4,6 +4,7 @@ import { getSiteSettings } from "@/utils/siteSettings";
import { notFound } from 'next/navigation';
import DynamicListPage from "@/components/common/DynamicListPage";
import { Suspense } from 'react';
import { getPageData } from "@/utils/data";
const baseSlug = 'products';
@ -19,13 +20,8 @@ export async function generateMetadata({ params }) {
const resolvedParams = await params;
const slug = resolvedParams.slug || [];
const slugArr = [baseSlug, ...(Array.isArray(slug) ? slug : [slug])];
const res = await fetch(`${process.env.SITE_URL}/api/get-page-data`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ slug_list: slugArr })
});
const json = await res.json();
const { data, error, page_info } = json;
const { data, error, page_info } = await getPageData({ slug_list: slugArr });
if (error) {
return {
title: error.title || 'Page Error',
@ -52,14 +48,13 @@ export default async function Page({ params }) {
const siteSettings = await getSiteSettings(process.env.SITE_URL);
const pageSize = Number(siteSettings.page_size) || 12;
// Fetch only page 1 for initial static render
const res = await fetch(`${process.env.SITE_URL}/api/get-page-data`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ slug_list: slugArr, page: 1, page_size: pageSize })
const { data, error, total } = await getPageData({
slug_list: slugArr,
page: 1,
page_size: pageSize,
downloadFiles: true,
});
const result = await res.json();
const { data, error, total } = result;
if (error) {
notFound();
}

View File

@ -22,6 +22,7 @@ export default function SwiperItemsUI({ data, items, ...props }) {
const subtitle = data?.subtitle || props.subtitle;
const category_slug = data?.t3 || props.category_slug || "";
const columns = data?.t5 || props.columns || 4;
const button_text = data?.button_text || props.button_text || "查看详情";
const rows = (data?.p1 && !isNaN(Number(data.p1))) ? Number(data.p1) : (props.rows || 1);
// Loading and error states are now handled by the server component and React Suspense.
@ -190,7 +191,7 @@ export default function SwiperItemsUI({ data, items, ...props }) {
<span className="bg"></span>
<figcaption className="group-hover:opacity-100 absolute w-full h-full opacity-0 text-center px-4 py-3 inset-0 z-[5] pointer-events-none p-2">
<h5 className="from-top !mb-0 absolute w-full translate-y-[-80%] p-[.75rem_1rem] left-0 top-2/4">
查看详情
{button_text}
</h5>
</figcaption>
</Link>

Binary file not shown.

After

Width:  |  Height:  |  Size: 65 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 55 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 100 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 83 KiB