Kargzul
Kargzul

Reputation: 56

How to Access Local Files in Server Actions in Next.js 14.1 Deployed on Vercel

Content:

I've been struggling with an issue for quite some time while deploying my Next.js 14.1 app on Vercel. Specifically, I'm encountering problems when trying to access local files inside a server action. After searching through numerous StackOverflow threads and GenAI answers, I haven't found a viable solution.

Problem:

I have a server action that generates a PDF using a template.pdf file and several font files (e.g., .ttf files). These files are stored in the actions/pdf_data folder. However, in production, when I attempt to access these files, I get a ENOENT (file not found) error because the directory and files seem to be missing in the production environment.

What I've Tried:

I attempted to resolve this issue by adding the following configuration in my next.config.js file:

webpack: (config, { dev, isServer }) => {
    if (!dev && isServer) {
        config.plugins.push(
            new CopyPlugin({
                patterns: [
                    {
                        from: path.join(__dirname, "actions", "pdf_data"),
                        to: path.join(
                            __dirname,
                            ".next",
                            "server",
                            "actions",
                            "pdf_data",
                        ),
                    },
                ],
            }),
        );
    }

    return config;
},

However, when I try to read the files during the execution of my server action in production, the directory structure inside the .next/server folder looks like this:

directory contents: [  'app',  'app-paths-manifest.json',  'chunks',  'font-manifest.json',  'middleware-build-manifest.js',  'middleware-manifest.json',  'middleware-react-loadable-manifest.js',  'next-font-manifest.js',  'next-font-manifest.json',  'pages',  'pages-manifest.json',  'server-reference-manifest.js',  'server-reference-manifest.json',  'webpack-runtime.js']

It appears that my solution doesn't work because, in the Vercel edge environment, only certain files are generated, rather than the full .next bundle that exists at build time.

Potential Solution:

I'm considering importing the necessary files directly within the server action, as this might force the build process to bundle the files correctly. However, I'm not sure if this approach will work or how to implement it effectively.

Has anyone encountered a similar issue or found a reliable solution for accessing local files in server actions on Vercel?

PS: I am aware of the option to use @vercel/blob for storing files, but I would prefer not to use it, as it significantly increases the time required to generate the PDF. I'm looking for an alternative solution that avoids this overhead.

My Simplified Server Action Code:

"use server";

import { PDFDocument, PDFFont, PDFImage, rgb } from "pdf-lib";
import * as fs from "fs";
import * as path from "path";
import { User } from "@/utils/userTypes";
import { db, storage, getCurrentUser } from "@/lib/firebase/firebase-admin";
import fontkit from "@pdf-lib/fontkit";
import sharp from "sharp";

export default async function generatePDF(id: string) {

  const pdfDataDir = path.join(process.cwd(), ".next/server/actions/pdf_data");

  const [templatePath, arialPath, arialItalicsPath, arialBoldPath] = [
    "template.pdf",
    "ARIAL.TTF",
    "ARIALI.TTF",
    "ARIALBD.TTF",
  ].map((filename) => path.join(pdfDataDir, filename));

  logger.info("File paths:", {
    templatePath,
    arialPath,
    arialItalicsPath,
    arialBoldPath,
  });

  [templatePath, arialPath, arialItalicsPath, arialBoldPath].forEach(
    (filePath) => {
      if (!fs.existsSync(filePath)) {
        logger.error(`File not found at path: ${filePath}`);
        throw new Error(`File not found at path: ${filePath}`);
      }
    },
  );

  const [pdfDoc, arialFont, arialItalicsFont, arialBoldFont] = 
    await Promise.all([
      PDFDocument.load(fs.readFileSync(templatePath)),
      fs.promises.readFile(arialPath),
      fs.promises.readFile(arialItalicsPath),
      fs.promises.readFile(arialBoldPath),
    ]);

  pdfDoc.registerFontkit(fontkit);
  const [arial, arialItalics, arialBold, dbData] = await Promise.all([
    pdfDoc.embedFont(arialFont),
    pdfDoc.embedFont(arialItalicsFont),
    pdfDoc.embedFont(arialBoldFont),
    getInfoFromDB(id),
  ]);

  ...
  ...
  
  const base64String = Buffer.from(pdfBytes).toString("base64");
  return base64String;
}

The discussion: https://github.com/vercel/next.js/discussions/70125

My current solution:

Switched from server action to api route and I can access my files accordingly.

As much as my problem is solved I'm still trying to figure out if there's a way to fix this in any other way while using server actions

Upvotes: 1

Views: 486

Answers (0)

Related Questions