refactor: 完成静态网站转换,移除所有 CMS 和动态功能
- 删除数据库相关代码 (src/db/) - 删除 API 路由 (src/app/api/) - 删除认证相关代码 (src/lib/auth/, src/providers/) - 删除监控和安全中间件 (src/lib/security/, src/lib/monitoring/) - 删除 hooks (use-news, use-products, use-services) - 更新组件为静态数据源 - 添加 nginx 静态配置和部署脚本 - 添加 static-link 组件
This commit is contained in:
@@ -1,18 +1,21 @@
|
||||
'use client';
|
||||
|
||||
import { useRouter } from 'next/navigation';
|
||||
import { ArrowLeft } from 'lucide-react';
|
||||
import { Button } from '@/components/ui/button';
|
||||
|
||||
/**
|
||||
* BackButton - 统一的返回按钮组件
|
||||
*
|
||||
* 在纯静态导出模式下使用 window.history.back() 替代 Next.js 的 router.back(),
|
||||
* 确保在无服务端路由的环境下正常工作。
|
||||
*/
|
||||
export function BackButton() {
|
||||
const router = useRouter();
|
||||
|
||||
return (
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
className="text-[#5C5C5C] hover:text-[#C41E3A] hover:bg-transparent h-auto py-2 px-3"
|
||||
onClick={() => router.back()}
|
||||
onClick={() => window.history.back()}
|
||||
>
|
||||
<ArrowLeft className="w-4 h-4 mr-2" />
|
||||
返回
|
||||
|
||||
@@ -0,0 +1,76 @@
|
||||
'use client';
|
||||
|
||||
import { useCallback, type AnchorHTMLAttributes, type MouseEventHandler, type ReactNode } from 'react';
|
||||
|
||||
/**
|
||||
* StaticLink - 纯静态站点专用链接组件
|
||||
*
|
||||
* 在 output: 'export' 模式下,Next.js 的客户端路由会拦截所有站内 <a> 标签的点击,
|
||||
* 尝试发送 RSC 请求,导致 "Failed to fetch RSC payload" 错误。
|
||||
*
|
||||
* 本组件通过 e.preventDefault() 阻止 Next.js 拦截,然后根据情况导航:
|
||||
* - 有外部 onClick:只阻止拦截,由外部 onClick 控制导航
|
||||
* - 外部链接 / 新窗口:不拦截,保持默认行为
|
||||
* - Hash 链接:平滑滚动
|
||||
* - 站内链接:window.location.href 完整页面导航
|
||||
*
|
||||
* @see https://github.com/vercel/next.js/issues/85374
|
||||
*/
|
||||
interface StaticLinkProps extends AnchorHTMLAttributes<HTMLAnchorElement> {
|
||||
children: ReactNode;
|
||||
href: string;
|
||||
}
|
||||
|
||||
function isExternalLink(href: string): boolean {
|
||||
return href.startsWith('http://') || href.startsWith('https://') || href.startsWith('mailto:') || href.startsWith('tel:');
|
||||
}
|
||||
|
||||
export function StaticLink({ children, href, onClick, target, rel, ...props }: StaticLinkProps) {
|
||||
const handleClick: MouseEventHandler<HTMLAnchorElement> = useCallback(
|
||||
(e) => {
|
||||
// 外部链接或新窗口打开:不拦截,保持默认行为
|
||||
if (isExternalLink(href) || target === '_blank') {
|
||||
onClick?.(e);
|
||||
return;
|
||||
}
|
||||
|
||||
// 阻止 Next.js 客户端路由拦截
|
||||
e.preventDefault();
|
||||
|
||||
// 如果有外部 onClick,由它完全控制导航行为
|
||||
if (onClick) {
|
||||
onClick(e);
|
||||
return;
|
||||
}
|
||||
|
||||
// Hash 链接:平滑滚动
|
||||
if (href.includes('#')) {
|
||||
const [path, hash] = href.split('#');
|
||||
if (path && path !== window.location.pathname) {
|
||||
window.location.href = href;
|
||||
} else if (hash) {
|
||||
const el = document.getElementById(hash);
|
||||
if (el) {
|
||||
el.scrollIntoView({ behavior: 'smooth' });
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// 站内页面链接:完整页面导航
|
||||
window.location.href = href;
|
||||
},
|
||||
[href, onClick, target]
|
||||
);
|
||||
|
||||
// 外部链接自动添加安全属性
|
||||
const linkRel = isExternalLink(href)
|
||||
? 'noopener noreferrer'
|
||||
: rel;
|
||||
|
||||
return (
|
||||
<a href={href} onClick={handleClick} target={target} rel={linkRel} {...props}>
|
||||
{children}
|
||||
</a>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user