Next.js 15中Server Action读取静态文件部署失败问题
部署后Server Action读取文件失败问题
复现步骤
- 访问vibrant-ui部署后的站点,进入docs/components/month-slider页面
- 依次点击:Components > Month Slider > Code
预期结果
组件应正常显示代码。
当前情况
页面显示错误提示:Failed to read file
问题详情
我编写了一个Server Action用于读取components/vibrant/component-name.tsx路径下的静态组件文件,代码如下:
Server Action 代码
"use server" import { promises as fs } from "fs" import path from "path" import { z } from "zod" // 定义响应类型 type CodeResponse = { content?: string error?: string details?: string } // 输入验证规则 const fileSchema = z.object({ fileName: z.string().min(1), }) export async function getFileContent(fileName: string): Promise<CodeResponse> { // 验证输入参数 try { fileSchema.parse({ fileName }) } catch { return { error: "File parameter is required", } } try { // 安全拼接文件路径 const filePath = path.join(process.cwd(), "components", "vibrant", fileName) const content = await fs.readFile(filePath, "utf8") return { content } } catch (error) { console.error("读取文件出错:", error) const errorMessage = error instanceof Error ? error.message : "未知错误" return { error: "Failed to read file", details: errorMessage, } } }
我在客户端组件中调用该Server Action,代码如下:
客户端调用组件代码
"use client" import { getFileContent } from "@/app/actions/file" import { Button } from "@/components/ui/button" import { cn } from "@/lib/utils" import { Check, Copy } from "lucide-react" import { useEffect, useState } from "react" import { Prism as SyntaxHighlighter } from "react-syntax-highlighter" import { oneDark } from "react-syntax-highlighter/dist/esm/styles/prism" type Props = { source: string language?: string } export const CodeBlock = ({ source, language = "typescript" }: Props) => { const [code, setCode] = useState("") const [error, setError] = useState("") const [copied, setCopied] = useState(false) const [isExpanded, setIsExpanded] = useState(false) useEffect(() => { const fetchCode = async () => { const result = await getFileContent(source) if (result.error) { setError(result.error) setCode("") return } if (result.content) { setCode(result.content) setError("") } } fetchCode() }, [source]) const copyToClipboard = async () => { try { await navigator.clipboard.writeText(code) setCopied(true) setTimeout(() => setCopied(false), 2000) } catch (err) { console.error("复制文本失败: ", err) } } if (error) { return <div className="p-4 bg-red-50 text-red-600 rounded-lg">{error}</div> } return ( <div className="relative w-full"> <Button size="icon" onClick={copyToClipboard} className={cn( "absolute top-4 right-6 p-2 bg-white hover:bg-gray-100 transition-colors rounded-full", isExpanded && "right-2" )} aria-label="复制代码" > {copied ? ( <Check className="w-4 h-4 text-green-500" /> ) : ( <Copy className="w-4 h-4 text-black" /> )} </Button> <SyntaxHighlighter language={language} style={oneDark} className={cn("w-full", isExpanded ? "h-full" : "h-[480px]")} > {code} </SyntaxHighlighter> <div className="absolute bottom-0 left-0 right-4 flex justify-center pb-2"> <div className={cn( "backdrop-blur-sm bg-transparent p-1 w-full h-16 flex items-center justify-center", isExpanded && "backdrop-blur-none" )} > <Button onClick={() => setIsExpanded(!isExpanded)} className="bg-white hover:bg-gray-100 text-black rounded-full" size="sm" > {isExpanded ? "收起" : "查看全部"} </Button> </div> </div> </div> ) }
本地环境下,执行dev和prod命令均能正常运行,但部署到线上后,请求Server Action时出现读取文件失败的错误。
内容的提问来源于stack exchange,提问作者Ala Eddine Menai
相关产品推荐
相关产品推荐

