Next.js: As rotas da API Next.js (e páginas) devem suportar a leitura de arquivos

Criado em 5 ago. 2019  ·  87Comentários  ·  Fonte: vercel/next.js

Solicitação de recurso

Sua solicitação de recurso está relacionada a um problema? Por favor descreva.

Atualmente não é possível ler arquivos de rotas ou páginas de API.

Descreva a solução que você gostaria

Eu quero ser capaz de chamar fs.readFile com um caminho __dirname e "simplesmente funcionar".

Isso deve funcionar no modo de desenvolvimento e produção.

Descreva as alternativas que você considerou

Isso pode precisar ser integrado com @zeit/webpack-asset-relocator-loader em alguma capacidade. Este plugin lida com esses tipos de requisitos.

No entanto, não é uma necessidade. Eu estaria OK com algo que _somente_ funcione com __dirname e __filename (nenhum caminho relativo ou baseado em cwd).

Contexto adicional

Exemplo:

// 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'
  )
  // ...
}

Nota: Eu sei que você pode enganar o exemplo acima ☝️ com require , mas esse não é o ponto. 😄

story feature request

Comentários muito úteis

Solução alternativa que estou usando:

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

e no local você precisa do caminho

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'))

Sei que isso não resolve a necessidade de fazer referência a arquivos com caminhos relativos ao arquivo atual, mas resolve meu caso de uso muito relacionado (ler arquivos de imagem de uma pasta /public/images ).

Todos 87 comentários

Só queria concordar com isso, tentando implementar o upload de arquivos usando rotas de API. Posso fazer o upload do arquivo, mas preciso acessá-lo novamente para fazer o upload para o balde S3.

Eu apoio isso! Além disso, poder ler diretórios é muito importante para o uso da minha empresa, pois mantemos nossos dados como membros da equipe e postagens de blog em um diretório de conteúdo, portanto, estamos procurando uma maneira de exigir todos os arquivos do diretório.

O PR acima vai consertar isso! ☝️ 🙏

Que tal fs.writeFile isso é possível? Por exemplo, crie e salve um arquivo JSON baseado em um webhook postado em um /api/route

Olá @marlonmarcello , isso vai ser possível. Fique ligado 😊

Já está resolvido?

Ainda não, você pode se inscrever para # 8334

@ huv1k Muito obrigado!

Existe uma maneira de ajudar isso a avançar mais rapidamente?

Vale a pena notar: se você estiver usando TypeScript, já pode importar um arquivo JSON como um módulo diretamente (certifique-se de que resolveJsonModule seja true em tsconfig.json ). Por exemplo:

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

A forma do objeto JSON também é usada automaticamente como seu tipo, portanto, o preenchimento automático é muito bom.

Solução alternativa que estou usando:

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

e no local você precisa do caminho

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'))

Sei que isso não resolve a necessidade de fazer referência a arquivos com caminhos relativos ao arquivo atual, mas resolve meu caso de uso muito relacionado (ler arquivos de imagem de uma pasta /public/images ).

Vi no PR que isso mudou um pouco - alguma atualização sobre quais são os planos atuais (ou não são)? Parece que existem algumas estratégias que você não deseja seguir, lembre-se de listá-las + por que os colaboradores podem tentar?

Isso está bloqueando o uso de nexo com Next.js. Seria ótimo ver isso priorizado novamente.

Solução alternativa que estou usando:

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

e no local você precisa do caminho

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'))

Sei que isso não resolve a necessidade de fazer referência a arquivos com caminhos relativos ao arquivo atual, mas resolve meu caso de uso muito relacionado (ler arquivos de imagem de uma pasta /public/images ).

Homem incrível. Funcionou para mim.

Tenho usado o novo método getStaticProps para isso (em # 9524). O método está atualmente marcado como instável, mas parece haver um bom suporte da equipe Next.js para enviá-lo oficialmente.

por exemplo:

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 Você tem algum projeto de fonte pública em que está usando isso? Curioso sobre como seria.

O projeto não é de código aberto, ainda, mas estou feliz em compartilhar mais da minha configuração se você tiver mais perguntas.

@ ScottSmith95 eu tenho _todas_ as perguntas 😛

  1. Onde em seu projeto você armazena os arquivos de dados? (fora / dentro src ?)
  2. Qual é a aparência de um componente de página next.js que os usa?
  3. São apenas caminhos embutidos em código ou você pode carregar um arquivo com base em parâmetros de caminho?
  4. Como funciona a construção / implantação, especialmente se não forem caminhos embutidos em código?

@Svish Nós armazenamos arquivos de dados em / data dentro de nosso projeto. (As páginas estão em / pages, não em / src / prages.) Este componente de página se parece com isto (os adereços são enviados para o componente Home, que é a exportação padrão):

// /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>
    </>
  );
};

Para páginas mais avançadas, aquelas com rotas dinâmicas, pegamos esses dados assim:

// /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 }
  };
}

A implantação ocorre sem problemas, com rotas dinâmicas, getStaticPaths() torna-se necessário. Recomendo que você verifique o RFC para obter a documentação sobre isso, mas aqui está um exemplo de como lidamos com isso reunindo todos os dados dos membros da nossa equipe e passando-os para 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 Parece promissor! Algumas perguntas de acompanhamento, se você tiver tempo:

  1. O que você está fazendo aqui é para geração de sites estáticos? Ou seja, ao usar next export ?
  2. Eu entendi corretamente, que getStaticPaths retorna uma lista de parâmetros de caminho, que é então (a seguir) alimentada, um por um, em getStaticProps para cada renderização?
  3. Você pode usar getStaticProps sem getStaticPaths , por exemplo, para uma página sem parâmetros?
  4. Você pode usar getStaticProps em _app ? Por exemplo, se você tem alguma configuração de todo o site que gostaria de carregar ou algo parecido?

E as ápis ?? Esses ganchos são para páginas, mas e quanto às apis?

Estou confuso. Consegui definir o _dirname como uma variável env na próxima configuração. Portanto, consegui acessar o sistema de arquivos a partir da API, mas só funcionou localmente. Depois de implantá-lo até agora, recebi um erro. Alguma ideia de por que não funcionará após a implantação?

@ josias-r o problema principal é geralmente que os arquivos a serem lidos não estão incluídos na implantação, mas depende de como você os inclui e de quais tipos de arquivos eles são ( js / json normalmente não tem problema, mas outros tipos de arquivo como .jade exigirão maneiras alternativas de lidar com o seu, como usar um @now/node lambda / deployment separado para ler / manipular esses arquivos).

Se você puder explicar mais sobre o erro, talvez alguém possa ajudá-lo.

@BrunoBernardino Na verdade, se referia aos arquivos JSON dentro da minha pasta src. Mas, na verdade, é até mesmo o método fs.readdirSync(my_dirname_env_var) que já falha na implantação. Portanto, esse dir não parece existir após a implantação. Aqui está o que eu obtenho quando tento acessar o caminho completo para o json vis minha API:

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

E como mencionei, isso funciona localmente quando eu construo e, em seguida, executo npm start .

@ josias-r Obrigado! Você já tentou fazer fs.readdirSync com um caminho relativo (sem variáveis) em vez disso (apenas para depurar a implantação)? Descobri que geralmente funciona e, se assim for, você pode escrever esse trecho de código (apenas lendo o arquivo, não armazenando-o em qualquer lugar) em algum lugar de um processo de inicialização ( getInitialProps ou algo assim), para que o o processo de implantação identifica que ele precisa desse arquivo e, em seguida, continua lendo-o com o var no código / lógica real. Não é legal, mas funciona até que seja compatível. Acredito que também usar __dirname funciona em alguns casos.

@BrunoBernardino Consegui construir uma árvore de arquivos a partir do caminho relativo à raiz ./ . O que consegui foi o seguinte JSON (sem os módulos de nó listados):

{
  "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"
    }
  ]
}

Parece que seu arquivo JSON está faltando. Você tentou incluí-lo por meio do código como sugeri acima? O principal problema é que as otimizações que a implantação executa nem sempre pegam caminhos dinâmicos, acredito, então forçar um caminho estático funcionou para mim no passado (não necessariamente para o código em execução real, mas para garantir que os arquivos relevantes estão incluídos). Isso faz sentido?

@BrunoBernardino Mudei para uma solução sem API. Como desejo exigir arquivos de uma pasta dinamicamente e só preciso do conteúdo desses arquivos, posso usar o método import() . Eu só não queria fazer dessa maneira, porque parece hacky, mas está essencialmente fazendo a mesma coisa que meu endpoint de API faria.
... Tentei colocar o arquivo na pasta estática, mas também não funcionou. Mas espero que o acesso ao sistema de arquivos seja possível no futuro.

Eu também tive que recorrer a soluções hacky, mas espero que isso aconteça em breve e mais pessoas começarão a ver o Next como pronto para produção à medida que esses casos de uso se tornem suportados "como esperado".

Solução alternativa que estou usando:

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

e no local você precisa do caminho

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'))

Sei que isso não resolve a necessidade de fazer referência a arquivos com caminhos relativos ao arquivo atual, mas resolve meu caso de uso muito relacionado (ler arquivos de imagem de uma pasta /public/images ).

Homem incrível. Funcionou para mim.

Ele funciona perfeitamente no desenvolvimento local, embora não pareça funcionar ao implantar em 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'
}

Eu entendo que a pasta pública foi movida para a rota, então tentei forçá-la a pesquisar na pasta base quando em produção, mas ainda obtive o mesmo resultado:

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 o problema que você está enfrentando é porque não é um arquivo .json , .js ou .ts . Os arquivos em /public são "implantados" em um CDN, mas não no lambda (AFAIK), portanto, para esse caso, você precisa de uma implantação lambda dedicada ( @now/node ) com includeFiles , ou, se você só precisa daquele único arquivo, converta-o em base64 e use-o como uma var (em um arquivo dedicado ou não).

Obrigado @BrunoBernardino esperava tanto, vou usar o método base64

É alguma resolução para o __dirname no ambiente implantado ??

@NicolasHz você pode explicar? Não entendi muito bem sua pergunta.

@BrunoBernardino Olhando os últimos comentários, incluindo o meu, tenho certeza de que o hack "mapear _dirname na próxima configuração" não funciona na implantação. Mesmo com arquivos js e JSON. Pelo menos para implantação now , isso provavelmente não conta para implantações personalizadas.

@BrunoBernardino Não estou conseguindo usar algumas variáveis ​​que apontam para o caminho local no env implementado. __dirname é indefinido depois de implantado e não consigo ler um arquivo de meus scripts apis.

Entendi @NicolasHz . Sim, você precisará recorrer a uma das soluções acima, dependendo de qual tipo de arquivo você precisa ler / acessar.

Apenas confirmando, o config.js não está funcionando nas implantações.

Solução alternativa que estou usando:

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

e na definição da API onde eu preciso do caminho (a pasta allPosts contém todos os blogs no formato markdown e está localizada na raiz do projeto)

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

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

Está funcionando perfeitamente no desenvolvimento local.
Mas está dando esse erro ao implantar para zeit agora.

[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'
}

@sjcodebook como @BrunoQuaresma disse, essa solução alternativa só funciona localmente. Ainda estou usando uma implantação @now/node separada para lambdas para acessar o sistema de arquivos e chamar esse arquivo por meio de uma solicitação do próprio aplicativo (ou gerar qualquer resultado estático de que preciso antes de implantar). Meio louco, mas funciona.

Oi @BrunoBernardino ... Você quer dizer um projeto separado com um servidor de nó personalizado?

No entanto, não entendo por que há uma configuração " includeFiles " se é impossível acessá-los 🤔

@valse pode estar no mesmo projeto. Aqui está um trecho do meu 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"
    }
  ]
}

Dessa forma, posso chamá-los por meio de algo como:

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

de dentro de uma próxima página da API, supondo que eu tenha um arquivo lambdas/email.ts que lida com o envio de e-mails e a leitura de arquivos de modelo como pug .

Espero que ajude!

Além disso, "includeFiles" só funciona para @now/node (talvez outros, mas não @now/next )

@BrunoBernardino parece estar usando funções node , agora não pode ler ESM!

isso é o que acontece quando tento importar uma lista de páginas mdx:

código

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([])
}

o erro que recebo:

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

SyntaxError: Unexpected token 'export'

@talentlessguy Não estou na equipe Zeit / Vercel, apenas um cliente feliz. Parece que isso pode ser mais adequado para o suporte ao cliente, pois vejo alguns problemas potenciais apenas a partir desse snippet:

  1. Você pode querer usar apenas nada ou __dirname vez de process.cwd() para o caminho base. Eu não usei este último em lambdas, mas os outros, então não tenho certeza se isso é um problema ou não
  2. Você está importando NextApiRequest e NextApiResponse como tipos, mas isso deve estar executando a partir de @now/node" , certo? Portanto, os tipos devem ser importados como:
import { NowRequest, NowResponse } from '@now/node';
  1. Você está importando / lendo de pages/... mas os está incluindo por meio de includeFiles ? Qual é a aparência do seu now.json ?

@BrunoBernardino

Não posso usar __dirname porque é sempre / , process.cwd() vez disso, mostra o caminho real

Aceitei nossas correções e funcionou:

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([])
}

now.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"
    }
  ]
}

erro que recebo:

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

SyntaxError: Cannot use import statement outside a module

Parece que a função de nó de digitação não pode tratar .mdx como um módulo :(

Tudo bem, parece que você encontrou o problema. Tente ler o conteúdo do arquivo e analisá-lo em vez de importar diretamente. Eu nunca vi uma importação como essa funcionar, e parece algo que só funcionaria com um pouco da magia do Babel, que você também pode usar em vez do simples TS.

@BrunoBernardino você está certo, mas não está claro ... Eu tenho o alvo definido para esnext e o módulo para esnext também, ele deve ser capaz de importar tudo ... mas de alguma forma não

de qualquer forma, não está relacionado ao problema, vou pesquisar em algum lugar

Sem problemas. Algumas dicas podem estar em https://mdxjs.com/advanced/typescript e https://mdxjs.com/getting-started/webpack, o que pode fazer com que a implantação @now/node precise ser ajustada para use-o. De qualquer forma, o apoio deles deve ajudar.

qualquer movimento sobre isso? Seria ótimo poder incluir modelos de e-mail html para uso em Rotas de API. No momento, estou incluindo-os em arquivos JS, mas não sou um fã particular desse hack.

Outro hack é usar o webpack raw-loader para embuti-los no js.

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

Em seguida, use templates.verify como uma string.

Há um problema acontecendo com o next-i18next que parece estar relacionado a este ( vercel / vercel # 4271 ). Basicamente, now não coloca os .json arquivos localizados dentro de /public/static/locales/ na função sem servidor. Alguém pode fornecer uma solução alternativa até que o recurso discutido aqui seja adicionado ao próximo?

@borispoehland , você tentou as soluções alternativas de importação / solicitação acima? Isso deve funcionar.

@borispoehland , você tentou as soluções alternativas de importação / solicitação acima? Isso deve funcionar.

@BrunoBernardino Não sei exatamente o que comentário você quer dizer.

Você pode me dar um exemplo de como importar de alguma forma todos os .json arquivos dentro de public/static/locales para a função sem servidor? E onde fazer isso (em qual arquivo)?

Estou usando o próximo (como você afirmou anteriormente, includeFiles não é compatível com @now/next , idk se isso tiver algum impacto no meu problema).

Além disso, como next-i18next é uma espécie de caixa preta para mim (portanto, não quero importar os arquivos de lá), procuro uma maneira de importá-los inteiramente para que next-i18next possam diretamente acessá-los (em outros comentários acima, às vezes apenas o PROJECT_DIRNAME era definido dentro do next.config.json e a importação tinha que ser feita manualmente. Não é isso que tento alcançar). Como em vercel / vercel # 4271 , eu só quero que now leve meus .json arquivos para a função sem servidor de alguma forma.

@borispoehland em _qualquer_ arquivo dentro de pages/api (ou que seja chamado por alguém lá), faça algo como https://github.com/vercel/next.js/issues/8251#issuecomment -544008976

Você não precisa fazer nada com a importação. A questão é que o webpack vercel executado verá que esses arquivos precisam ser incluídos e deve funcionar.

Espero que faça sentido.

@borispoehland em _qualquer_ arquivo dentro de pages/api (ou que seja chamado por alguém lá), faça algo como # 8251 (comentário)

Você não precisa fazer nada com a importação. A questão é que o webpack vercel executado verá que esses arquivos precisam ser incluídos e deve funcionar.

Espero que faça sentido.

@BrunoBernardino o problema com essa abordagem é que tenho muitos arquivos json. Fazer a importação manual de cada arquivo é meio complicado. Existe uma maneira mais fácil de dizer a now : "Ei, pegue todos os arquivos json dentro desse diretório recursivamente"? desde já, obrigado

Editar: Mesmo importar manualmente json arquivos resulta no mesmo erro que antes. Vou abrir um novo problema para isso, acho

Abri uma nova edição para o meu problema , caso alguém esteja interessado em entrar na discussão. Obrigado por agora, @BrunoBernardino !

Outra opção / solução alternativa para habilitar a capacidade de usar __dirname como você normalmente esperaria que se comportasse é ajustar a configuração do webpack.

Por padrão, o webpack criará um alias para vários Node globais com polyfills, a menos que você diga a ele para não:
https://webpack.js.org/configuration/node/
E as configurações padrão do webpack são para deixar __dirname e __filename sozinhos, ou seja, não polyfill e deixar que o nó os trate normalmente.

No entanto, a configuração do webpack Next.js não usa / reflete os padrões do webpack https://github.com/vercel/next.js/blob/bb6ae2648ddfb65a810edf6ff90a86201d52320c/packages/next/build/webpack-config.ts#L661 -L663

Dito isso, usei o plugin de configuração Next customizado abaixo para ajustar a configuração do webpack.

IMPORTANTE: isso funciona para meu caso de uso. Ele não foi testado em uma ampla gama de ambientes / configurações nem foi testado em todos os testes de unidade / integração Next.js. Seu uso pode ter efeitos colaterais indesejados em seu ambiente.
Além disso, o Next pode ter razões específicas para não usar as configurações padrão do webpack para __dirname e __filename . Portanto, novamente, o código abaixo pode ter efeitos colaterais indesejados e deve ser usado com cautela.

Além disso, o plug-in abaixo foi projetado para uso com o pacote next-compose-plugins : https://github.com/cyrilwanner/next-compose-plugins

Mas também deve funcionar como um plugin normal: https://nextjs.org/docs/api-reference/next.config.js/custom-webpack-config

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;
    },
  });
};

Implementei a solução por @jkjustjoshing e, embora funcione muito bem localmente, não funciona quando implanto o aplicativo no Vercel.

Estou tendo o erro a seguir:

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

Meu código:

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

Aqui está um link para o arquivo: https://github.com/bengrunfeld/trend-viewer/blob/master/pages/api/graphql-data.js

@bengrunfeld sim, sua solução só funciona localmente.

Tive um problema semelhante recentemente (queria ler um arquivo em uma rota de API) e a solução foi mais fácil do que o esperado.

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

@borispoehland Muito obrigado !! Sua solução funcionou perfeitamente!

@bengrunfeld sem problemas, também descobri por coincidência ( @BrunoBernardino ;)). É a solução para o problema de todos aqui, eu acho.

Observe que você ainda precisa definir next.config.js . Excluí o arquivo depois de ver que a solução de

Em seguida, redefini-lo para a solução de @jkjustjoshing acima, implantado novamente no Vercel e funcionou.

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

Observe que você ainda precisa definir next.config.js . Eu o removi depois de ver que a solução de @borispoehland funcionava e recebi um erro semelhante.

Eu o redefini para a solução de @jkjustjoshing acima,

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

@bengrunfeld Sério? Talvez você ainda esteja usando a abordagem PROJECT_ROOT em outro ponto do código, porque no meu projeto funciona sem ela. Como é o erro?

Ao implantar no Vercel, como escrevo readFile em uma página que funcionará no modo SSG e SSR / visualização?

Repositório de demonstração de onde não funciona: https://github.com/mathdroid/blog-fs-demo

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

@mathdroid tente mover readFile e readdir dentro das funções getStaticProps e getStaticPaths , respectivamente, caso contrário, o código pode ser executado no navegador.

Importar fs deve estar bem no início, no entanto.

Deixe-nos saber como isso funciona.

@borispoehland Obrigado pela solução maravilhosa. Não esperava que path.resolve() a /public funcionasse tanto localmente quanto em Vercel: olhos :! Você é meu salvador por hoje. : +1:

@borispoehland eu tentei sua solução dentro de uma função sem servidor, mas ainda consigo:
ENOENT: nenhum arquivo ou diretório, abra '/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;
}

eu tentei com ou sem o next.config.js

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

Talvez sua solução não funcione em funções sem servidor?

@borispoehland eu tentei sua solução dentro de uma função sem servidor, mas ainda consigo:
ENOENT: nenhum arquivo ou diretório, abra '/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;
}

eu tentei com ou sem o next.config.js

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

Talvez sua solução não funcione em funções sem servidor?

Eu não sei porque não funciona no seu lado ... Desculpe

Ok, fiz funcionar para leitura usando o código @bengrunfeld , mas infelizmente você não pode escrever:
[Erro: EROFS: sistema de arquivos somente leitura, abra '/var/task/public/posts.json']
Portanto, não há como atualizar um cache para evitar muitas chamadas de banco de dados :(

@neckaros , você tentou usar minha abordagem para ler um arquivo diferente de .json , por exemplo, um arquivo .jpg ?

Ok, fiz funcionar para leitura usando o código @bengrunfeld , mas infelizmente você não pode escrever:

[Erro: EROFS: sistema de arquivos somente leitura, abra '/var/task/public/posts.json']

Portanto, não há como atualizar um cache para evitar muitas chamadas de banco de dados :(

@neckaros você deve ser capaz de escrever e ler do S3 (ou algum outro sistema de arquivos externo), mas eu geralmente uso o redis para coisas rápidas em cache que podem ser voláteis. https://redislabs.com o mantém "sem servidor", e tenho exemplos de códigos prontos para produção em https://nextjs-boilerplates.brn.sh, se desejar.

@borispoehland eu conseguia ler, mas não escrever na função sem servidor. Mas acabei fazendo com que ele funcionasse atualizando meu cache nas compilações incrementais (revalidar) em vez de adicionar novo conteúdo. O que eu acho que não é um padrão ruim. Obrigado pela ajuda!

@BrunoBernardino obrigado vou dar uma olhada. Estou realmente procurando uma solução para amadores totalmente gratuita que não quebra depois de alguns usuários :)

Estou realmente procurando uma solução para amadores totalmente gratuita que não quebra depois de alguns usuários :)

Entendido. RedisLabs e Vercel fizeram isso por mim. 💯

Depois de alguma escavação, comecei a escrever arquivos para trabalhar com o pacote de sistema operacional estendido ...

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)

ler um arquivo funciona com a solução @subwaymatch
const logoPath = path.resolve('./public/logo.png')

Depois de alguma escavação, comecei a escrever arquivos para trabalhar com o pacote de sistema operacional estendido ...

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)

ler um arquivo funciona com a solução @subwaymatch
const logoPath = path.resolve('./public/logo.png')

Legal, você consegue ler o conteúdo deste arquivo? O diretório é acessível e permanente?

@marklundin Com uma função chamada tmpdir duvido que seja permanente, mas se funcionar, seria bom saber o quão temporário realmente tmpdir é, sim ... 🤔

Alguma atualização sobre isso? Estou me perguntando por que funciona em getInitialProps, mas não em rotas de API 🤷‍♂️

Minha solução alternativa atual

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

atualmente tendo esse problema nas rotas de API também

atualmente tendo esse problema nas rotas de API também

Existem algumas soluções de trabalho aqui, qual problema você está tendo, especificamente?

Estou lutando para fazer isso funcionar, mesmo com sugestões deste tópico. Meu caso de uso é que estou escrevendo um guia e quero mostrar o código-fonte do componente ao lado do próprio componente. Meu método para fazer isso é usar fs para carregar o arquivo jsx do componente dentro de getServerSideProps e passar o valor da string do conteúdo do arquivo como um prop.

Eu estava me sentindo muito feliz por tê-lo funcionando localmente, mas quando fui implantá-lo, a alegria se foi :(

Consulte: https://github.com/ElGoorf/i18next-guide/blob/fix-example-components/pages/plurals.jsx

@ElGoorf, seu problema é que os arquivos public estão no limite e as funções estão em um lambda. Agora, @vercel/next ainda não permite includeFiles , então a maneira mais fácil de fazê-lo funcionar seria usar uma função lambda com ele.

Aqui estão alguns exemplos de código que ajudaram outros aqui: https://github.com/vercel/next.js/issues/8251#issuecomment -614220305

Obrigado @BrunoBernardino Não percebi que tinha sentido falta de "x itens ocultos carregam mais ..." e pensei que estava ficando louco com o tópico perdendo significado!

Infelizmente, tive problemas com sua solução, pois é a primeira vez que ouço falar de Edge / Lambda, no entanto, descobri que a solução de @balthild estava mais próxima do que eu queria originalmente antes de tentar o método node.fs: https: / /github.com/vercel/next.js/issues/8251#issuecomment -634829189

Excelente! Você conseguiu fazer funcionar? Ou você ainda está tendo problemas?

Não tenho certeza se Vercel usa essa terminologia, mas por Edge quero dizer CDN, de onde os arquivos estáticos são servidos, e por lambda quero dizer as funções de "back-end" que são chamadas das rotas de API, que são isoladas como funções AWS Lambda .

Ei,

Alguma atualização sobre a gravação de arquivos usando next.js no vercel? Eu consigo ler sem problemas. Usando o const logoPath = path.resolve('./public/logo.png')

Estou tentando sobrescrever o arquivo public / sitemap.xml (devido aos limites de tamanho no vercel). Só posso devolvê-lo sem erros como um arquivo estático na pasta pública. Eu implementei anteriormente o mapa do site com zlib e streaming da resposta, mas parece esperar até que o stream seja concluído e, em seguida, retorne-o. Isso não atinge o erro de limitação de tamanho, mas infelizmente é muito lento. Estou aberto a qualquer sugestão que as pessoas possam ter. O mapa do site é criado a partir de uma chamada de API para um back-end separado e precisa ser atualizado regularmente.

Coisas que tentei:

  • Compactar e transmitir o xml - funciona, mas muito lento.
  • Construir e retornar o sitemap de uma função de API, infelizmente, isso atinge o limite de tamanho.
  • Ler o arquivo xml estático da pasta pública (funciona) independentemente do tamanho, mas não atualizável.
  • O teste de gravação neste arquivo não funciona. O teste de gravação em qualquer arquivo / pasta não funciona
  • Teste o retorno do arquivo xml estático da função "api", erro de tamanho. Isso funciona localmente.
  • Teste o retorno do arquivo xml estático da "página" getServerSideProp size error. Isso funciona localmente.
  • Gostaria de receber alguma ideia?

Ei @emomooney , não imagino o Vercel permitindo escrever arquivos em uma função (mesmo para cache), já que a principal "vantagem" do serverless é a ausência de estado, e isso adicionaria um estado a ele, então acho que você precisa usar o edge / cdn para isso.

Eu implementei anteriormente o mapa do site com zlib e streaming da resposta, mas parece esperar até que o stream seja concluído e, em seguida, retorne-o.

Estou curioso para saber se você estava experimentando essa lentidão apenas para chamadas subsequentes, ou apenas a primeira, para a inicialização a frio? Imagino que seja uma chamada de API para Vercel por meio de uma função de api next.js, ou um lambda dedicado, semelhante ao que faço aqui .

Se fosse e ainda fosse muito lento, seu "back-end separado" está fora do Vercel? Nesse caso, você pode usá-lo potencialmente para construir um arquivo sitemap.xml e vercel --prod em um domínio, basicamente "armazenando em cache" o arquivo para que fique legível e acessível, e você só precisa atualizá-lo o robots.txt para vincular o mapa do site a outro domínio / subdomínio.

Esta página foi útil?
0 / 5 - 0 avaliações

Questões relacionadas

flybayer picture flybayer  ·  3Comentários

formula349 picture formula349  ·  3Comentários

YarivGilad picture YarivGilad  ·  3Comentários

swrdfish picture swrdfish  ·  3Comentários

olifante picture olifante  ·  3Comentários