Next.js: Next.js API 路由(和页面)应该支持读取文件

创建于 2019-08-05  ·  87评论  ·  资料来源: vercel/next.js

功能要求

您的功能请求是否与问题有关? 请描述。

目前无法从 API 路由或页面读取文件。

描述您想要的解决方案

我希望能够使用__dirname路径调用fs.readFile并让它“正常工作”。

这应该在开发和生产模式下工作。

描述您考虑过的替代方案

这可能需要在某些情况下与@zeit/webpack-asset-relocator-loader集成。 这个插件处理这些类型的需求。

然而,这不是必需品。 我对 _only_ 与__dirname__filename (没有相对或基于 cwd 的路径)一起使用的东西没问题。

附加上下文

例子:

// pages/api/test.js
import fs from 'fs'
import path from 'path'

export default (req, res) => {
  const fileContent = fs.readFileSync(
    path.join(__dirname, '..', '..', 'package.json'), 
    'utf8'
  )
  // ...
}

注意:我知道你可以用require欺骗上面的例子☝️,但这不是重点。 😄

story feature request

最有用的评论

我正在使用的解决方法:

# next.config.js
module.exports = {
    serverRuntimeConfig: {
        PROJECT_ROOT: __dirname
    }
}

并在您需要路径的位置

import fs from 'fs'
import path from 'path'
import getConfig from 'next/config'
const { serverRuntimeConfig } = getConfig()

fs.readFile(path.join(serverRuntimeConfig.PROJECT_ROOT, './path/to/file.json'))

我知道这并不能解决使用相对于当前文件的路径来引用文件的需要,但这解决了我非常相关的用例(从/public/images文件夹读取图像文件)。

所有87条评论

只是想补充一下,尝试使用 API 路由实现文件上传。 我可以上传文件,但随后需要能够再次访问它以将其上传到 S3 存储桶。

我第二个! 此外,能够读取目录对于我公司的使用非常重要,因为我们将团队成员和博客文章等数据保存在内容目录中,因此我们正在寻找一种方法来要求目录中的所有文件。

上面的 PR 会解决这个问题! ☝️🙏

fs.writeFile可能吗? 例如,基于发布在/api/route上的 webhook 创建并保存一个 JSON 文件

@marlonmarcello ,这是可能的。 敬请关注😊

这个已经解决了?

还没有,可以订阅#8334

@huv1k非常感谢!

有没有办法帮助这件事更快地推进?

值得注意的是:如果您使用的是 TypeScript,您已经可以直接将 JSON 文件作为模块导入(确保resolveJsonModuletrue中的tsconfig.json )。 例如:

import myJson from '../../../some/path/my.json';

JSON 对象的形状也被自动用作其类型,因此自动完成非常好。

我正在使用的解决方法:

# next.config.js
module.exports = {
    serverRuntimeConfig: {
        PROJECT_ROOT: __dirname
    }
}

并在您需要路径的位置

import fs from 'fs'
import path from 'path'
import getConfig from 'next/config'
const { serverRuntimeConfig } = getConfig()

fs.readFile(path.join(serverRuntimeConfig.PROJECT_ROOT, './path/to/file.json'))

我知道这并不能解决使用相对于当前文件的路径来引用文件的需要,但这解决了我非常相关的用例(从/public/images文件夹读取图像文件)。

在 PR 中看到这已经发生了一些变化 - 有关当前计划是(或不是)的任何更新? 听起来有些策略你不想采用,介意列出它们+为什么贡献者可以试一试?

阻止了nexus与 Next.js的使用。 很高兴再次看到这一点。

我正在使用的解决方法:

# next.config.js
module.exports = {
    serverRuntimeConfig: {
        PROJECT_ROOT: __dirname
    }
}

并在您需要路径的位置

import fs from 'fs'
import path from 'path'
import getConfig from 'next/config'
const { serverRuntimeConfig } = getConfig()

fs.readFile(path.join(serverRuntimeConfig.PROJECT_ROOT, './path/to/file.json'))

我知道这并不能解决使用相对于当前文件的路径来引用文件的需要,但这解决了我非常相关的用例(从/public/images文件夹读取图像文件)。

了不起的男人。 为我工作。

我一直在为此使用新的getStaticProps方法(在 #9524 中)。 该方法目前被标记为不稳定,但 Next.js 团队似乎对正式发布它提供了很好的支持。

例如:

export async function unstable_getStaticProps() {
  const siteData = await import("../data/pages/siteData.json");
  const home = await import("../data/pages/home.json");

  return {
    props: { siteData, home }
  };
}

@ScottSmith95你有一些使用它的公共源项目吗? 好奇它会是什么样子。

该项目尚未开源,但如果您有更多问题,我很乐意分享更多我的配置。

@ScottSmith95我有_所有_的问题😛

  1. 在您的项目中,您将数据文件存储在何处? (外部/内部src ?)
  2. 使用它们的 next.js 页面组件是什么样的?
  3. 它是仅硬编码路径,还是可以根据路径参数加载文件?
  4. 构建/部署如何工作,尤其是当它不是硬编码路径时?

@Svish我们将数据文件存储在项目中的 /data 中。 (页面在 /pages 中,而不是 /src/prages。)这个页面组件看起来像这样(props 被发送到默认导出的 Home 组件):

// /pages/index.js
const Home = ({ siteData, home }) => {
  return (
    <>
      <Head>
        <meta name="description" content={siteData.siteDescription} />
        <meta name="og:description" content={siteData.siteDescription} />
        <meta
          name="og:image"
          content={getAbsoluteUrl(siteData.siteImage, constants.siteMeta.url)}
        />
      </Head>
      <section className={`container--fluid ${styles.hero}`}>
        <SectionHeader section={home.hero} heading="1">
          <div className="col-xs-12">
            <PrimaryLink
              href={home.hero.action.path}
              className={styles.heroAction}
            >
              {home.hero.action.text}
            </PrimaryLink>
          </div>
        </SectionHeader>
        <div className={styles.imageGradientOverlay}>
          <img src={home.hero.image.src} alt={home.hero.image.alt} />
        </div>
      </section>
    </>
  );
};

对于更高级的页面,那些具有动态路由的页面,我们像这样获取这些数据:

// /pages/studio/[member.js]
export async function unstable_getStaticProps({ params }) {
  const siteData = await import("../../data/pages/siteData.json");
  const member = await import(`../../data/team/${params.member}.json`);

  return {
    props: { siteData, member }
  };
}

部署非常顺利,有了动态路由, getStaticPaths()变得必要。 我鼓励您查看RFC以获取相关文档,但这里有一个示例,说明我们如何通过收集所有团队成员数据并将其传递给 Next.js 来处理该问题。

// /pages/studio/[member.js]
export async function unstable_getStaticPaths() {
  const getSingleFileJson = async path => await import(`../../${path}`);

  // These utility functions come from `@asmallstudio/tinyutil` https://github.com/asmallstudio/tinyutil
  const directoryData = await getDirectory(
    "./data/team",
    ".json",
    getSingleFileJson,
    createSlugFromTitle
  );
  const directoryPaths = directoryData.reduce((pathsAccumulator, page) => {
    pathsAccumulator.push({
      params: {
        member: page.slug
      }
    });

    return pathsAccumulator;
  }, []);

  return directoryPaths;
}

@ ScottSmith95看起来很有希望! 如果你有时间,可以问几个后续问题:

  1. 你在这里做的是静态网站生成? 即当使用next export
  2. 我是否理解正确, getStaticPaths返回一个路径参数列表,然后(接下来)将其逐个输入getStaticProps以进行每次渲染?
  3. 您可以在没有getStaticPaths getStaticProps情况下使用
  4. 你能在_app使用getStaticProps _app吗? 例如,如果您有一些站点范围的配置,您想加载或类似的东西?

api呢?? 那些钩子是用于页面的,但是 apis 呢?

我糊涂了。 我能够在下一个配置中将 _dirname 设置为环境变量。 因此,我能够从 API 访问文件系统,但它只能在本地工作。 部署到现在,出现错误。 任何想法为什么它在部署后不起作用?

@josias-r 主要问题通常是要读取的文件不包含在部署中,但这取决于您如何包含它们以及它们是哪种类型的文件( js / json通常很好,但其他文件类型(如.jade将需要其他方式来处理他的文件,例如使用单独的@now/node lambda/deployment 来读取/处理这些文件)。

如果你能解释更多关于错误的信息,也许有人可以帮助你。

@BrunoBernardino它实际上是指我的 src 文件夹中的 JSON 文件。 但实际上甚至是fs.readdirSync(my_dirname_env_var)方法在部署中已经失败了。 因此,部署后该目录似乎根本不存在。 这是我尝试通过 API 访问 json 的完整路径时得到的结果:

ERROR   Error: ENOENT: no such file or directory, open '/zeit/3fc37db3/src/content/somejsonfilethatexists.json'

正如我所提到的,当我构建然后运行npm start时,这在本地工作。

@josias-r 谢谢! 您是否尝试过使用相对路径(无变量)执行fs.readdirSync (仅用于调试部署)? 我发现这通常可以工作,如果是这样,您可以在初始化过程( getInitialProps或其他东西)中的某处编写那段代码(只是读取文件,而不是将其存储在任何地方),以便部署过程发现它需要该文件,然后继续使用实际代码/逻辑中的 var 读取它。 它并不整洁,但在支持此功能之前它可以工作。 我相信在某些情况下也可以使用__dirname

@BrunoBernardino我能够从根相对路径./开始构建一个文件树。 我得到的是以下 JSON(没有列出节点模块):

{
  "path": "./",
  "name": ".",
  "type": "folder",
  "children": [
    {
      "path": ".//.next",
      "name": ".next",
      "type": "folder",
      "children": [
        {
          "path": ".//.next/serverless",
          "name": "serverless",
          "type": "folder",
          "children": [
            {
              "path": ".//.next/serverless/pages",
              "name": "pages",
              "type": "folder",
              "children": [
                {
                  "path": ".//.next/serverless/pages/api",
                  "name": "api",
                  "type": "folder",
                  "children": [
                    {
                      "path": ".//.next/serverless/pages/api/posts",
                      "name": "posts",
                      "type": "folder",
                      "children": [
                        {
                          "path": ".//.next/serverless/pages/api/posts/[...id].js",
                          "name": "[...id].js",
                          "type": "file"
                        }
                      ]
                    }
                  ]
                }
              ]
            }
          ]
        }
      ]
    },
    {
      "path": ".//node_modules",
      "name": "node_modules",
      "type": "folder",
      "children": ["alot of children here ofc"]
    },
    { "path": ".//now__bridge.js", "name": "now__bridge.js", "type": "file" },
    {
      "path": ".//now__launcher.js",
      "name": "now__launcher.js",
      "type": "file"
    }
  ]
}

您的 JSON 文件似乎在那里丢失,您是否尝试通过我上面建议的代码包含它? 主要问题是部署运行的优化并不总是选择动态路径,我相信,所以强制静态路径在过去对我有用(不一定适用于实际运行的代码,但要确保相关文件被包含在内)。 这有意义吗?

@BrunoBernardino我已经切换到非 API 解决方案。 由于我动态地希望从文件夹中获取文件,而我只需要这些文件的内容,因此我可以使用import()方法。 我只是不想这样做,因为它看起来很笨拙,但它本质上与我的 API 端点所做的相同。
...我尝试将文件放入静态文件夹,但这也不起作用。 但我希望将来可以访问文件系统。

我也不得不求助于 hacky 解决方案,但希望这会很快落地,随着这些用例“按预期”得到支持,更多人将开始将 Next 视为生产就绪。

我正在使用的解决方法:

# next.config.js
module.exports = {
    serverRuntimeConfig: {
        PROJECT_ROOT: __dirname
    }
}

并在您需要路径的位置

import fs from 'fs'
import path from 'path'
import getConfig from 'next/config'
const { serverRuntimeConfig } = getConfig()

fs.readFile(path.join(serverRuntimeConfig.PROJECT_ROOT, './path/to/file.json'))

我知道这并不能解决使用相对于当前文件的路径来引用文件的需要,但这解决了我非常相关的用例(从/public/images文件夹读取图像文件)。

了不起的男人。 为我工作。

它在本地开发中完美运行,但在部署到now时似乎不起作用。

ENOENT: no such file or directory, open '/zeit/41c233e5/public/images/my-image.png'
    at Object.openSync (fs.js:440:3)
    at Object.readFileSync (fs.js:342:35)
    at getEmailImage (/var/task/.next/serverless/pages/api/contact/demo.js:123:52)
    at module.exports.7gUS.__webpack_exports__.default (/var/task/.next/serverless/pages/api/contact/demo.js:419:87)
    at processTicksAndRejections (internal/process/task_queues.js:93:5)
    at async apiResolver (/var/task/node_modules/next/dist/next-server/server/api-utils.js:42:9) {
  errno: -2,
  syscall: 'open',
  code: 'ENOENT',
  path: '/zeit/41c233e5/public/images/my-image.png'
}

我知道公共文件夹被移动到了路由,所以我试图强制它在生产时在基本文件夹中搜索,但仍然得到相同的结果:

ENOENT: no such file or directory, open '/zeit/5fed13e9/images/my-image.png'
    at Object.openSync (fs.js:440:3)
    at Object.readFileSync (fs.js:342:35)
    at getEmailImage (/var/task/.next/serverless/pages/api/contact/demo.js:124:52)
    at module.exports.7gUS.__webpack_exports__.default (/var/task/.next/serverless/pages/api/contact/demo.js:331:87)
    at processTicksAndRejections (internal/process/task_queues.js:93:5)
    at async apiResolver (/var/task/node_modules/next/dist/next-server/server/api-utils.js:42:9) {
  errno: -2,
  syscall: 'open',
  code: 'ENOENT',
  path: '/zeit/5fed13e9/images/my-image.png'
}

@PaulPCIO您遇到的问题是因为它不是.json.js.ts文件。 /public下的文件被“部署”到 CDN 而不是 lambda (AFAIK),因此对于这种情况,您需要使用includeFiles进行专用的 lambda ( @now/node ) 部署includeFiles ,或者,如果您只需要该单个文件,请将其转换为base64并将其用作 var(在专用文件中与否)。

谢谢@BrunoBernardino ,我将使用base64方法

这是部署环境中 __dirname 的一些解决方案?

@NicolasHz你能详细说明一下吗? 我不太明白你的问题。

@BrunoBernardino查看最后的评论,包括我的评论,我很确定“下一个配置中的映射_dirname ”hack 在部署中不起作用。 甚至带有 js 和 JSON 文件。 至少对于now部署而言,这可能不算用于自定义部署。

@BrunoBernardino我无法使用一些指向已部署环境中本地路径的变量。 __dirname 它在部署后未定义,并且我无法从我的 apis 脚本中读取文件。

明白了@NicolasHz 。 是的,您需要采用上述解决方案之一,具体取决于您需要读取/访问的文件类型。

只是确认一下,config.js 不适用于部署。

我正在使用的解决方法:

# next.config.js
module.exports = {
  env: {
    PROJECT_DIRNAME: __dirname,
  },
}

并在我需要路径的 api 定义中(allPosts 文件夹包含 Markdown 格式的所有博客,它位于项目根目录中)

import fs from 'fs'
import { join } from 'path'

const postsDirectory = join(process.env.PROJECT_DIRNAME, 'allPosts')

它在本地发展方面运作良好。
但是现在部署到 zeit 时会出现此错误。

[POST] /api/postsApi
11:00:13:67
Status:
500
Duration:
8.1ms
Memory Used:
76 MB
ID:
kxq8t-1585546213659-5c3393750f30
User Agent:
axios/0.19.2
{
  fields: [ 'title', 'date', 'slug', 'author', 'coverImage', 'excerpt' ],
  page: 1
}
2020-03-30T05:30:13.688Z    572075eb-4a7a-47de-be16-072a9f7005f7    ERROR   Error: ENOENT: no such file or directory, scandir '/zeit/1cc63678/allPosts'
    at Object.readdirSync (fs.js:871:3)
    at getPostSlugs (/var/task/.next/serverless/pages/api/postsApi.js:306:52)
    at module.exports.fZHd.__webpack_exports__.default (/var/task/.next/serverless/pages/api/postsApi.js:253:86)
    at apiResolver (/var/task/node_modules/next/dist/next-server/server/api-utils.js:48:15)
    at processTicksAndRejections (internal/process/task_queues.js:97:5) {
  errno: -2,
  syscall: 'scandir',
  code: 'ENOENT',
  path: '/zeit/1cc63678/allPosts'
}

@BrunoQuaresma @sjcodebook说,该解决方法仅适用于本地。 我仍然使用单独的@now/node部署 lambda 来访问文件系统,并通过来自应用程序本身的请求调用该文件(或在部署之前生成我需要的任何静态结果)。 有点疯狂,但它有效。

嗨@BrunoBernardino ...你是说一个带有自定义节点服务器的单独项目吗?

但是我不明白为什么有一个“ includeFiles ”设置,如果这样就无法访问它们🤔

@valse它可以在同一个项目中。 这是我的now.json的片段:

{
  "builds": [
    {
      "src": "next.config.js",
      "use": "@now/next"
    },
    {
      "src": "lambdas/**/*.ts",
      "use": "@now/node",
      "config": {
        "includeFiles": ["email-templates/**"]
      }
    }
  ],
  "routes": [
    {
      "src": "/lambdas/(.+)",
      "dest": "/lambdas/$1.ts"
    }
  ]
}

这样我就可以通过以下方式调用它们:

await ky.post(`${hostUrl}/lambdas/email?token=${someToken}`);

从下一个 api 页面内部,假设我有一个lambdas/email.ts文件,它处理发送电子邮件和读取模板文件,如pug

我希望这有帮助!

此外,“includeFiles”仅适用于@now/node (也许是其他人,但不适用于@now/next

@BrunoBernardino看起来如果使用node函数,它现在无法读取 ESM!

这是当我尝试导入 mdx 页面列表时发生的情况:

代码

import { NextApiRequest, NextApiResponse } from 'next'
import { promises as fs } from 'fs'
import { join } from 'path'
const { readdir } = fs

export default async (req: NextApiRequest, res: NextApiResponse) => {
  const postFiles = await readdir(join(process.cwd(), 'pages', 'blog'))

  const postNames: string[] = postFiles.filter((page: string) => page !== 'index.tsx')

  const posts = []

  for (const post of postNames) {
    const mod = await import(`../pages/blog/${post}`)

    posts.push({ ...mod, link: post.slice(0, post.indexOf('.')) })
  }

  res.status(200).json([])
}

我得到的错误:

export const title = 'My new website!'
^^^^^^

SyntaxError: Unexpected token 'export'

@talentlessguy我不是 Zeit/Vercel 团队的成员,只是一个快乐的客户。 似乎这可能更适合他们的客户支持,因为我仅从该片段中看到了一些潜在问题:

  1. 您可能只想使用任何内容或__dirname而不是process.cwd()作为基本路径。 我没有在 lambdas 中使用后者,但其他人,所以我不确定这是否是一个问题
  2. 您将NextApiRequestNextApiResponse作为类型导入,但这应该从@now/node" ,对吗? 所以类型应该像这样导入:
import { NowRequest, NowResponse } from '@now/node';
  1. 您正在从pages/...导入/读取,但是您是否通过includeFiles包含它们? 你的now.json什么样子的?

@布鲁诺伯纳迪诺

我不能使用__dirname因为它总是/ ,而是process.cwd()显示真实路径

我接受了你的修复并且它起作用了:

lambdas/posts.ts

import { NowResponse, NowRequest } from '@now/node'
import { promises as fs } from 'fs'
import { join } from 'path'
const { readdir } = fs

export default async (req: NowRequest, res: NowResponse) => {
  const postFiles = await readdir(join(process.cwd(), 'pages', 'blog'))

  const postNames: string[] = postFiles.filter((page: string) => page !== 'index.tsx')

  const posts = []

  for (const post of postNames) {
    const mod = await import(`../pages/blog/${post}`)

    posts.push({ ...mod, link: post.slice(0, post.indexOf('.')) })
  }

  res.status(200).json([])
}

现在.json

{
  "builds": [
    {
      "src": "next.config.js",
      "use": "@now/next"
    },
    {
      "src": "lambdas/**/*.ts",
      "use": "@now/node",
      "config": {
        "includeFiles": ["pages/blog/*.mdx"]
      }
    }
  ],
  "routes": [
    {
      "src": "/lambdas/(.+)",
      "dest": "/lambdas/$1.ts"
    }
  ]
}

我得到的错误:

import Meta from '../../components/Article/Meta.tsx'
^^^^^^

SyntaxError: Cannot use import statement outside a module

看起来打字稿节点函数不能将.mdx视为模块:(

好的,看来你找到了问题所在。 尝试读取文件内容并解析它们,而不是直接导入。 我从来没有见过这样的导入,而且它似乎只适用于一些 Babel 魔法,也欢迎你使用它而不是普通的 TS。

@BrunoBernardino你是对的,但这不是简单的 ts ......我将目标设置为 esnext,模块也设置为 esnext,它应该能够导入所有内容......但不知何故它没有

无论如何它与问题无关,会在某处谷歌它

不用担心。 一些提示可能在https://mdxjs.com/advanced/typescripthttps://mdxjs.com/getting-started/webpack 中,这可能会使@now/node部署需要调整为用它。 无论如何,他们的支持应该会有所帮助。

对此有何动向? 能够包含用于 API 路由的 html 电子邮件模板会很棒。 现在我将它们包含在 JS 文件中,但我不是这个 hack 的特别粉丝。

另一个 hack 是使用 webpack raw-loader 将它们嵌入到 js 中。

yarn add --dev raw-loader
const templates = {
    verify: require("raw-loader!../template/email/verify.hbs").default,
};

然后使用templates.verify作为字符串。

next-i18next 出现了一个问题,似乎与这个 ( vercel/vercel#4271 ) 有关。 基本上now不会将位于/public/static/locales/.json文件放入无服务器函数中。 在将此处讨论的功能添加到下一个之前,任何人都可以提供解决方法吗?

@borispoehland您是否尝试

@borispoehland您是否尝试

@BrunoBernardino我不知道你的意思是什么确切的评论。

你能给我一个例子,以某种方式将public/static/locales所有.json文件导入到无服务器函数中吗? 在哪里执行此操作(在哪个文件中)?

我正在使用 next (正如您之前所说, includeFiles@now/next不兼容,如果这对我的问题有任何影响,请确认)。

此外,因为next-i18next对我来说是一种黑匣子(因此我不想从那里导入文件),我寻找一种完全导入它们的方法,以便next-i18next可以直接访问它们(在上面的其他评论中,有时只有PROJECT_DIRNAME中定义了next.config.json并且必须手动完成导入。这不是我想要达到的)。 就像在vercel/vercel#4271 中一样,我只希望now以某种方式将我的.json文件放入无服务器函数中。

@borispoehlandpages/api内的_any_文件中(或者在那里被一个人调用),做一些类似https://github.com/vercel/next.js/issues/8251#issuecomment -544008976 的事情

您无需对导入执行任何操作。 关键是 webpack vercel 运行时会看到需要包含这些文件,它应该可以工作。

我希望这是有道理的。

@borispoehlandpages/api内的_any_文件中(或者在那里被一个人调用),做一些类似#8251 的事情

您无需对导入执行任何操作。 关键是 webpack vercel 运行时会看到需要包含这些文件,它应该可以工作。

我希望这是有道理的。

@BrunoBernardino这种方法的问题是我有很多 json 文件。 为每个文件手动导入有点麻烦。 有没有更简单的方法来告诉now :“嘿,请递归地提取该目录中的所有 json 文件”? 提前致谢

编辑:即使手动导入json文件也会导致与以前相同的错误。 我想为此开一个新问题

为我的问题打开了一个新问题,以防有人有兴趣加入讨论。 现在谢谢@BrunoBernardino

启用__dirname使用能力的另一个选项/解决方法是调整 webpack 配置。

默认情况下,webpack 会使用 polyfill 为各种 Node 全局变量设置别名,除非你告诉它不要:
https://webpack.js.org/configuration/node/
而 webpack 默认设置是让__dirname__filename单独存在,即不填充它们并让节点正常处理它们。

但是,Next.js webpack 配置不使用/反映 webpack 默认值https://github.com/vercel/next.js/blob/bb6ae2648ddfb65a810edf6ff90a86201d52320c/packages/next/build/webpack-config.ts#L661 -L663

综上所述,我使用了下面的自定义 Next 配置插件来调整 webpack 配置。

重要提示:这适用于我的用例。 它尚未在广泛的环境/配置中进行测试,也未针对所有 Next.js 单元/集成测试进行测试。 使用它可能会对您的环境产生意想不到的副作用。
此外,Next 可能有特定原因不使用__dirname__filename的 webpack 默认设置。 同样,下面的代码可能会产生意想不到的副作用,应谨慎使用。

此外,以下插件设计用于next-compose-plugins包: https :

但也应该作为普通插件工作: https :

const withCustomWebpack = (nextCfg) => {
  return Object.assign({}, nextCfg, {
    webpack(webpackConfig, options) {
      // We only want to change the `server` webpack config.
      if (options.isServer) {
        // set `__dirname: false` and/or `__filename: false` here to align with webpack defaults:
        // https://webpack.js.org/configuration/node/
        Object.assign(webpackConfig.node, { __dirname: false });
      }

      if (typeof nextCfg.webpack === 'function') {
        return nextCfg.webpack(webpackConfig, options);
      }

      return webpackConfig;
    },
  });
};

我通过@jkjustjoshing实现了该解决方案,虽然它在本地运行良好,但当我将应用程序部署到 Vercel 时它不起作用。

我收到以下错误:

Error: GraphQL error: ENOENT: no such file or directory, open '/vercel/37166432/public/ts-data.csv'

我的代码:

const content = await fs.readFile(
  path.join(serverRuntimeConfig.PROJECT_ROOT, "./public/ts-data.csv")
);

这是文件的链接: https :

@bengrunfeld是的,您的解决方案仅适用于本地。

我最近遇到了类似的问题(想在 API 路由中读取文件)并且解决方案比预期的要容易。

试试path.resolve('./public/ts-data.csv')

@borispoehland 非常感谢!! 您的解决方案效果很好!

@bengrunfeld没问题,我也是偶然发现的( @BrunoBernardino ;))。 我想这是每个人的问题的解决方案。

请注意,您仍然需要设置next.config.js 。 我看到@borispoehland的解决方案有效后删除了该文件,并收到了类似的错误。

然后我将它重置为@jkjustjoshing上面的解决方案,再次部署到 Vercel,它起作用了。

# next.config.js
module.exports = {
    serverRuntimeConfig: {
        PROJECT_ROOT: __dirname
    }
}

请注意,您仍然需要设置next.config.js 。 我看到@borispoehland的解决方案有效

我将它重置为上面@jkjustjoshing的解决方案,再次部署到 Vercel,并且它起作用了。

# next.config.js
module.exports = {
    serverRuntimeConfig: {
        PROJECT_ROOT: __dirname
    }
}

@bengrunfeld真的吗? 也许您仍在代码中的另一点使用PROJECT_ROOT方法,因为在我的项目中,它可以在没有它的情况下工作。 错误看起来如何?

部署到 Vercel 时,如何在页面中写入readFile以同时在 SSG 和 SSR/预览模式下工作?

不起作用的演示存储库: https :

https://blog-fs-demo.vercel.app/

@mathdroid尝试分别在getStaticPropsgetStaticPaths函数中移动readFilereaddir ,否则代码可能会在浏览器中运行。

不过,导入fs应该没问题。

让我们知道它是如何工作的。

@borispoehland感谢您提供出色的解决方案。 没想到path.resolve()/public会在本地和 Vercel 上工作 :eyes:! 你是我今天的救星。 :+1:

@borispoehland我在无服务器功能中尝试了您的解决方案,但仍然得到:
ENOENT:没有这样的文件或目录,打开'/var/task/public/posts.json'

const postsFile = resolve('./public/posts.json');

const updateCache = async (posts: IPost[]): Promise<IPost[]> => {
    postCache = posts;
    fs.writeFileSync(postsFile, JSON.stringify(postCache)); // <====
    return postCache;
}

我尝试在没有 next.config.js 的情况下使用我们的

module.exports = {
    serverRuntimeConfig: {
        PROJECT_ROOT: __dirname
    }
}

也许您的解决方案不适用于无服务器功能?

@borispoehland我在无服务器功能中尝试了您的解决方案,但仍然得到:
ENOENT:没有这样的文件或目录,打开'/var/task/public/posts.json'

const postsFile = resolve('./public/posts.json');

const updateCache = async (posts: IPost[]): Promise<IPost[]> => {
    postCache = posts;
    fs.writeFileSync(postsFile, JSON.stringify(postCache)); // <====
    return postCache;
}

我尝试在没有 next.config.js 的情况下使用我们的

module.exports = {
    serverRuntimeConfig: {
        PROJECT_ROOT: __dirname
    }
}

也许您的解决方案不适用于无服务器功能?

我不知道为什么它对你不起作用......对不起

好的,我使用@bengrunfeld代码让它可以读取,但不幸的是你不能写:
[错误:EROFS:只读文件系统,打开'/var/task/public/posts.json']
因此无法更新缓存以避免过多的数据库调用:(

@neckaros您是否尝试过使用我的方法读取.json以外的文件,例如.jpg文件?

好的,我使用@bengrunfeld代码让它可以读取,但不幸的是你不能写:

[错误:EROFS:只读文件系统,打开'/var/task/public/posts.json']

因此无法更新缓存以避免过多的数据库调用:(

@neckaros你应该能够从 S3(或其他一些外部文件系统)写入和读取,但我通常使用 redis 来快速、缓存的东西可能是不稳定的。 https://redislabs.com使其保持“无服务器”状态,如果需要,我在https://nextjs-boilerplates.brn.sh 中提供了可用于生产的代码示例。

@borispoehland我可以从无服务器函数中读取但不能写入。 但是我最终通过在增量构建(重新验证)中刷新缓存而不是添加新内容来使其工作。 我想这不是一个糟糕的模式。 谢谢你的帮助!

@BrunoBernardino谢谢我去看看。 真的在寻找一个完全免费的业余爱好者解决方案,一旦你有几个用户就不会中断:)

真的在寻找一个完全免费的业余爱好者解决方案,一旦你有几个用户就不会中断:)

收到。 RedisLabs 和 Vercel 为我做到了这一点。 💯

经过一番挖掘,我开始使用扩展的 os 包编写文件...

import { tmpdir } from "os";
const doc = new PDFDocument()
const pdfPath = path.join(tmpdir(), `${store.id}${moment().format('YYYYMMDD')}.pdf`)
const writeStream = doc.pipe(fs.createWriteStream(pdfPath)

使用@subwaymatch解决方案读取文件
const logoPath = path.resolve('./public/logo.png')

经过一番挖掘,我开始使用扩展的 os 包编写文件...

import { tmpdir } from "os";
const doc = new PDFDocument()
const pdfPath = path.join(tmpdir(), `${store.id}${moment().format('YYYYMMDD')}.pdf`)
const writeStream = doc.pipe(fs.createWriteStream(pdfPath)

使用@subwaymatch解决方案读取文件
const logoPath = path.resolve('./public/logo.png')

很好,你能读回这个文件的内容吗? 该目录是否可访问且永久有效?

@marklundin有一个名为tmpdir的函数,我怀疑它是永久性的,但如果它有效,那么最好知道tmpdir实际上是多么临时,是的......🤔

有任何更新吗? 我想知道为什么它在 getInitialProps 中有效,而在 API 路由中无效 🤷‍♂️

我目前的解决方法

const data = await import(`../../../../data/de/my-nice-file.json`);
res.json(data.default);

目前在 API 路由中也有这个问题

目前在 API 路由中也有这个问题

这里有一些可行的解决方案,你有什么问题,特别是?

即使有这个线程的建议,我也很难让它工作。 我的用例是我正在编写指南并希望在组件本身旁边显示组件的源代码。 我这样做的方法是使用 fs 在 getServerSideProps 中加载组件的 jsx 文件,并将文件内容的字符串值作为 prop 传递。

我对让它在本地工作感到欣喜若狂,但是当我去部署它时,喜悦已经消失了:(

请参阅: https :

@ElGoorf你的问题是public文件在边缘,而函数在 lambda 上。 现在, @vercel/next仍然不允许includeFiles ,所以让它工作的最简单方法是使用lambda函数。

这是一些帮助其他人的示例代码: https :

谢谢@BrunoBernardino,我没有意识到我错过了“x 个隐藏项目加载更多......”,并认为我因为失去意义的线程而发疯了!

不幸的是,我在你的解决方案上遇到了困难,因为这是我第一次听说 Edge/Lambda,但是,我发现https:/ /github.com/vercel/next.js/issues/8251#issuecomment -634829189

伟大的! 你让它工作了吗? 或者你还有问题?

我不确定 Vercel 是否甚至使用该术语,但 Edge 我的意思是 CDN,从那里提供静态文件,而 lambda 我的意思是从 API 路由调用的“后端”函数,它们像 AWS Lambda 函数一样被隔离.

嘿,

在 vercel 上使用 next.js 写入文件的任何更新? 我可以阅读没有问题。 使用const logoPath = path.resolve('./public/logo.png')

我正在尝试覆盖 public/sitemap.xml 文件(由于 vercel 的大小限制)我只能将它作为 public 文件夹中的静态文件返回而不会出错。 我之前已经使用 zlib 实现了站点地图并流式传输响应,但它似乎要等到流完成然后返回它。 这不会达到大小限制错误,但不幸的是它非常慢。 我愿意接受人们可能提出的任何建议。 站点地图是通过对单独后端的 API 调用构建的,需要定期更新。

我尝试过的事情:

  • 压缩和流式传输 xml - 工作但速度很慢。
  • 从 api 函数构建和返回站点地图,不幸的是这达到了大小限制。
  • 无论大小如何,都从公共文件夹(有效)读取静态 xml 文件,但不可更新。
  • 测试写入此文件,不起作用。 测试写入任何文件/文件夹不起作用
  • 测试从“api”函数返回静态 xml 文件,大小错误。 这在本地有效。
  • 测试从“页面”返回静态 xml 文件 getServerSideProp 大小错误。 这在本地有效。
  • 会欣赏任何想法吗?

@emomooney ,我不认为 Vercel 允许在函数中写入文件(即使是缓存),因为无服务器的主要“优势”是它的无状态,这会为其添加状态,所以我想你会需要使用edge/cdn。

我之前已经使用 zlib 实现了站点地图并流式传输响应,但它似乎要等到流完成然后返回它。

我很好奇您是否只是在随后的呼叫中遇到这种缓慢,或者只是在冷启动时遇到这种缓慢? 我想这是通过 next.js api 函数或专用 lambda 对 Vercel 的 API 调用,类似于我在这里所做的

如果是,而且仍然太慢,那么您的“独立后端”是否在 Vercel 之外? 如果是这样,您可能会使用它来构建sitemap.xml文件并将vercel --prod构建到域中,基本上“缓存”文件以使其可读和可访问,您只需要更新robots.txt将站点地图链接到另一个域/子域。

此页面是否有帮助?
0 / 5 - 0 等级

相关问题

renatorib picture renatorib  ·  3评论

olifante picture olifante  ·  3评论

havefive picture havefive  ·  3评论

jesselee34 picture jesselee34  ·  3评论

lixiaoyan picture lixiaoyan  ·  3评论