Next.js: Las rutas (y páginas) de la API de Next.js deben admitir la lectura de archivos

Creado en 5 ago. 2019  ·  87Comentarios  ·  Fuente: vercel/next.js

Solicitud de función

¿Su solicitud de función está relacionada con un problema? Por favor describa.

Actualmente no es posible leer archivos de rutas o páginas API.

Describe la solución que te gustaría

Quiero poder llamar a fs.readFile con una ruta __dirname y hacer que "simplemente funcione".

Esto debería funcionar en el modo de desarrollo y producción.

Describe las alternativas que has considerado

Esto puede necesitar integrarse con @zeit/webpack-asset-relocator-loader en alguna capacidad. Este complemento maneja este tipo de requisitos.

Sin embargo, no es una necesidad. Estaría bien con algo que _sólo_ funcione con __dirname y __filename (sin rutas relativas o basadas en cwd).

Contexto adicional

Ejemplo:

// 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: Sé que puedes hacer trampa en el ejemplo anterior ☝️ con require , pero ese no es el punto. 😄

story feature request

Comentario más útil

Solución alternativa que estoy usando:

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

y en el lugar necesitas el camino

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

Sé que esto no resuelve la necesidad de hacer referencia a archivos con rutas relativas al archivo actual, pero esto resuelve mi caso de uso muy relacionado (leer archivos de imagen de una carpeta /public/images ).

Todos 87 comentarios

Solo quería respaldar eso, tratando de implementar la carga de archivos utilizando rutas API. Puedo hacer que el archivo se cargue, pero luego necesito poder acceder a él nuevamente para cargarlo en el depósito S3.

¡Secundo esto! Además, poder leer directorios es muy importante para el uso de mi empresa, ya que mantenemos nuestros datos, como los miembros del equipo y las publicaciones de blog, en un directorio de contenido, por lo que estamos buscando una manera de requerir todos los archivos en el directorio.

¡El PR anterior solucionará esto! ☝️ 🙏

¿Qué tal fs.writeFile es posible? Por ejemplo, cree y guarde un archivo JSON basado en un webhook que se publicó en un /api/route

Hola @marlonmarcello , esto va a ser posible. Estén atentos 😊

¿Esto ya está resuelto?

Todavía no, puedes suscribirte al # 8334

@ huv1k ¡ Muchas gracias!

¿Hay alguna manera de ayudar a que esto avance más rápidamente?

Vale la pena señalar: si está utilizando TypeScript, ya puede importar un archivo JSON como módulo directamente (asegúrese de que resolveJsonModule sea true en tsconfig.json ). P.ej:

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

La forma del objeto JSON también se usa automáticamente como su tipo, por lo que el autocompletado es realmente bueno.

Solución alternativa que estoy usando:

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

y en el lugar necesitas el camino

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

Sé que esto no resuelve la necesidad de hacer referencia a archivos con rutas relativas al archivo actual, pero esto resuelve mi caso de uso muy relacionado (leer archivos de imagen de una carpeta /public/images ).

Vi en PR que esto ha cambiado un poco: ¿alguna actualización sobre los planes actuales (o no)? Parece que hay algunas estrategias que no desea que se sigan, por favor enumerelas + ¿por qué los contribuyentes pueden intentarlo?

Esto está bloqueando el uso de nexus con Next.js. Sería genial ver esto priorizado nuevamente.

Solución alternativa que estoy usando:

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

y en el lugar necesitas el camino

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

Sé que esto no resuelve la necesidad de hacer referencia a archivos con rutas relativas al archivo actual, pero esto resuelve mi caso de uso muy relacionado (leer archivos de imagen de una carpeta /public/images ).

Hombre asombroso. Trabajó para mi.

He estado usando el nuevo método getStaticProps para esto (en # 9524). Actualmente, el método está marcado como inestable, pero parece haber un buen apoyo del equipo de Next.js para enviarlo oficialmente.

p.ej:

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 ¿Tiene algún proyecto de fuente pública en el que esté usando esto? Tengo curiosidad por saber cómo se vería.

El proyecto no es de código abierto, pero me complace compartir más de mi configuración si tiene más preguntas.

@ ScottSmith95 Tengo

  1. ¿En qué parte de su proyecto almacena los archivos de datos? (afuera / adentro src ?)
  2. ¿Cómo se ve un componente de página next.js que los usa?
  3. ¿Son solo rutas codificadas de forma rígida o puede cargar un archivo en función de los parámetros de la ruta?
  4. ¿Cómo funciona la compilación / implementación, especialmente si no se trata de rutas codificadas de forma rígida?

@Svish Almacenamos archivos de datos en / data dentro de nuestro proyecto. (Las páginas están en / pages, no en / src / prages). Este componente de página se ve así (los accesorios se envían al componente Inicio, que es la exportación predeterminada):

// /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 más avanzadas, aquellas con rutas dinámicas, tomamos estos datos así:

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

La implementación va muy bien, con rutas dinámicas, getStaticPaths() vuelve necesario. Le animo a que consulte el RFC para obtener la documentación sobre eso, pero aquí hay un ejemplo de cómo lo manejamos al recopilar todos los datos de los miembros de nuestro equipo y pasarlos a 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 prometedor! Un par de preguntas de seguimiento si tiene tiempo:

  1. ¿Qué estás haciendo aquí para la generación de sitios estáticos? Es decir, cuando se usa next export ?
  2. ¿Lo he entendido correctamente, que getStaticPaths devuelve una lista de parámetros de ruta, que luego (a continuación) se alimenta, uno por uno, en getStaticProps para cada render?
  3. ¿Puede usar getStaticProps sin getStaticPaths , por ejemplo, para una página sin ningún parámetro?
  4. ¿Puedes usar getStaticProps en _app ? Por ejemplo, si tiene alguna configuración para todo el sitio que le gustaría cargar o algo así.

¿Qué pasa con las apis? Esos ganchos son para páginas, pero ¿qué pasa con las apis?

Estoy confundido. Pude establecer el _dirname como una variable env en la siguiente configuración. Por lo tanto, pude acceder al sistema de archivos desde la API, pero solo funcionó localmente. Después de implementarlo hasta ahora, recibí un error. ¿Alguna idea de por qué no funcionará después de la implementación?

@ josias-r, el problema principal suele ser que los archivos a leer no están incluidos en la implementación, pero depende de cómo los incluyas y de qué tipo de archivos sean ( js / json generalmente está bien, pero otros tipos de archivos como .jade requerirán formas alternativas de lidiar con el suyo, como usar un @now/node lambda / deployment separado para leer / manejar esos archivos).

Si puede explicar más sobre el error, tal vez alguien pueda ayudarlo.

@BrunoBernardino En realidad, se refería a archivos JSON dentro de mi carpeta src. Pero en realidad es incluso el método fs.readdirSync(my_dirname_env_var) que ya falla en la implementación. Entonces ese dir no parece existir en absoluto después de la implementación. Esto es lo que obtengo cuando intento acceder a la ruta completa al json vis mi API:

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

Y como mencioné, esto funciona localmente cuando construyo y luego ejecuto npm start .

@ josias-r ¡Gracias! ¿Ha intentado hacer el fs.readdirSync con una ruta relativa (sin variables) en su lugar (solo para depurar la implementación)? Descubrí que normalmente funciona, y si es así, puedes escribir ese fragmento de código (solo leyendo el archivo, sin almacenarlo en ninguna parte) en algún lugar de un proceso de inicialización ( getInitialProps o algo así), de modo que el El proceso de implementación detecta que necesita ese archivo y luego sigue leyéndolo con la var en el código / lógica real. No es ordenado, pero funciona hasta que sea compatible. Creo que también el uso de __dirname funciona en algunos casos.

@BrunoBernardino Pude construir un árbol de archivos a partir de la ruta relativa a la raíz ./ . Lo que obtuve fue el siguiente JSON (sin los módulos de nodo enumerados):

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

Su archivo JSON parece faltar allí, ¿intentó incluirlo a través del código como sugerí anteriormente? El principal problema es que las optimizaciones que ejecuta la implementación no siempre recogen rutas dinámicas, creo, por lo que forzar una ruta estática me ha funcionado en el pasado (no necesariamente para el código real en ejecución, sino para asegurarme de que los archivos relevantes están incluidos). ¿Eso tiene sentido?

@BrunoBernardino Me import() . Simplemente no quería hacerlo de esta manera, porque parece un truco, pero esencialmente está haciendo lo mismo que habría hecho mi punto final de API.
... Intenté poner el archivo en la carpeta estática pero tampoco funcionó. Pero espero que en el futuro sea posible acceder al sistema de archivos.

También tuve que recurrir a soluciones hacky, pero espero que esto llegue pronto y más personas comenzarán a ver Next como listo para producción a medida que estos casos de uso sean compatibles "como se esperaba".

Solución alternativa que estoy usando:

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

y en el lugar necesitas el camino

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

Sé que esto no resuelve la necesidad de hacer referencia a archivos con rutas relativas al archivo actual, pero esto resuelve mi caso de uso muy relacionado (leer archivos de imagen de una carpeta /public/images ).

Hombre asombroso. Trabajó para mi.

Funciona perfectamente en el desarrollo local, aunque no parece funcionar cuando se implementa en 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'
}

Entiendo que la carpeta pública se mueve a la ruta, así que intenté forzarla a buscar en la carpeta base cuando estaba en producción, pero aún así obtuve el mismo 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, el problema que está experimentando allí es porque no es un archivo .json , .js o .ts . Los archivos bajo /public se "implementan" en una CDN pero no en la lambda (AFAIK), por lo que para ese caso necesita una implementación de lambda dedicada ( @now/node ) con includeFiles , o, si solo necesita ese archivo, conviértalo a base64 y úselo como var (en un archivo dedicado o no).

Gracias @BrunoBernardino esperaba tanto, base64

¿Es alguna resolución para el __dirname en el entorno implementado?

@NicolasHz, ¿ puedes

@BrunoBernardino Mirando los últimos comentarios, incluido el mío, estoy bastante seguro de que el truco "map _dirname en la siguiente configuración" no funciona en la implementación. Incluso archivos w / js y JSON. Al menos para la implementación de now , eso probablemente no cuenta para las implementaciones personalizadas.

@BrunoBernardino No puedo usar algunas variables que apuntan a la ruta local en el entorno implementado. __dirname no está definido una vez implementado y no puedo leer un archivo de mis scripts apis.

Lo tengo @NicolasHz . Sí, deberá recurrir a una de las soluciones anteriores, según el tipo de archivo que necesite leer / acceder.

Solo confirmando, config.js no está funcionando en implementaciones.

Solución alternativa que estoy usando:

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

y en la definición de api donde necesito la ruta (la carpeta allPosts contiene todos los blogs en formato de rebajas y se encuentra en la raíz del proyecto)

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

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

Está funcionando perfectamente en el desarrollo local.
Pero está dando este error al implementarlo en zeit ahora.

[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 dijo @BrunoQuaresma , esa solución solo funciona localmente. Todavía estoy usando una implementación @now/node separada para que lambdas acceda al sistema de archivos y llamo a ese archivo a través de una solicitud desde la aplicación (o genere cualquier resultado estático que necesite antes de la implementación). Un poco loco, pero funciona.

Hola @BrunoBernardino ... ¿Te refieres a un proyecto separado con un servidor de nodo personalizado?

Sin embargo, no entiendo por qué hay una configuración de "

@valse puede estar en el mismo proyecto. Aquí hay un fragmento de mi 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"
    }
  ]
}

De esa manera puedo llamarlos a través de algo como:

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

desde el interior de una siguiente página de API, asumiendo que tengo un archivo lambdas/email.ts que maneja el envío de correos electrónicos y la lectura de archivos de plantilla como pug .

¡Espero que eso ayude!

Además, "includeFiles" solo funciona para @now/node (tal vez otros, pero no @now/next )

@BrunoBernardino parece que si usa las funciones node , ¡ahora no puede leer ESM!

esto es lo que sucede cuando intento importar una 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([])
}

el error me sale:

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

SyntaxError: Unexpected token 'export'

@talentlessguy No estoy en el equipo de Zeit / Vercel, solo soy un cliente feliz. Parece que eso podría adaptarse mejor a su atención al cliente, ya que veo algunos problemas potenciales solo en ese fragmento:

  1. Es posible que desee usar simplemente nada o __dirname lugar de process.cwd() para la ruta base. No he usado este último en lambdas, pero los demás, así que no estoy seguro de si eso es un problema o no
  2. Está importando NextApiRequest y NextApiResponse como tipos, pero esto debería ejecutarse desde @now/node" , ¿verdad? Entonces, los tipos deben importarse como:
import { NowRequest, NowResponse } from '@now/node';
  1. Está importando / leyendo desde pages/... pero ¿los está incluyendo a través de includeFiles ? ¿Cómo se ve su now.json ?

@BrunoBernardino

No puedo usar __dirname porque siempre es / , process.cwd() lugar, muestra la ruta real

Acepté tus arreglos y funcionó:

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

error me sale:

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

SyntaxError: Cannot use import statement outside a module

Parece que la función de nodo mecanografiado no puede tratar .mdx como un módulo :(

Muy bien, parece que encontraste el problema. Intente leer el contenido del archivo y analizarlo en lugar de importarlo directamente. Nunca he visto una importación como esa funcionar, y parece algo que solo funcionaría con algo de magia de Babel, que también puedes usar en lugar de TS simple.

@BrunoBernardino , tienes razón, pero no es sencillo ... Tengo el objetivo configurado en esnext y el módulo en esnext también, debería poder importar todo ... pero de alguna manera no lo hace

de todos modos no está relacionado con el problema, lo buscaré en Google en algún lugar

No hay problema. Un par de consejos pueden estar en https://mdxjs.com/advanced/typescript y https://mdxjs.com/getting-started/webpack que podrían hacer que la implementación @now/node deba ajustarse para úselo. De todos modos, su apoyo debería ser de ayuda.

algún movimiento sobre esto? Sería genial poder incluir plantillas de correo electrónico html para usar en las rutas API. En este momento los estoy incluyendo en archivos JS pero no soy un fan particular de este truco.

Otro truco es usar el cargador de paquetes web para incrustarlos en js.

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

Luego use templates.verify como una cadena.

Hay un problema con next-i18next que parece estar relacionado con este ( vercel / vercel # 4271 ). Básicamente, now no coloca los archivos .json ubicados dentro de /public/static/locales/ en la función sin servidor. ¿Alguien puede proporcionar una solución alternativa hasta que la función que se analiza aquí se agregue a la siguiente?

@borispoehland, ¿ ha probado la importación / requiere soluciones alternativas desde arriba? Eso debería funcionar.

@borispoehland, ¿ ha probado la importación / requiere soluciones alternativas desde arriba? Eso debería funcionar.

@BrunoBernardino No sé qué comentario exacto quieres decir.

¿Puede darme un ejemplo de cómo importar de alguna manera todos los archivos .json dentro de public/static/locales en la función sin servidor? ¿Y dónde hacer esto (en qué archivo)?

Estoy usando el siguiente (como dijiste anteriormente, includeFiles no es compatible con @now/next , idk si esto tiene algún impacto en mi problema).

Además, debido a que next-i18next es una especie de caja negra para mí (por lo tanto, no quiero importar los archivos desde allí), busco una forma de importarlos por completo para que next-i18next puedan directamente acceder a ellos (en otros comentarios anteriores, a veces solo se definió PROJECT_DIRNAME dentro de next.config.json y la importación tuvo que hacerse manualmente. Esto no es lo que trato de alcanzar). Como en vercel / vercel # 4271 , solo quiero que now lleve mis archivos .json a la función sin servidor de alguna manera.

@borispoehland en el archivo _any_ dentro de pages/api (o que alguien lo llame allí), haga algo como https://github.com/vercel/next.js/issues/8251#issuecomment -544008976

No es necesario que haga nada con la importación. El punto es que las ejecuciones de vercel del paquete web verán que esos archivos deben incluirse, y debería funcionar.

Espero que tenga sentido.

@borispoehland en el archivo _any_ dentro de pages/api (o que alguien lo llame allí), haga algo como # 8251 (comentario)

No es necesario que haga nada con la importación. El punto es que las ejecuciones de vercel del paquete web verán que esos archivos deben incluirse, y debería funcionar.

Espero que tenga sentido.

@BrunoBernardino, el problema con este enfoque es que tengo muchos archivos json. Hacer la importación manualmente para cada archivo es algo engorroso. ¿Existe una manera más fácil de decirle a now : "Oye, por favor, recoja todos los archivos json dentro de ese directorio de forma recursiva"? Gracias por adelantado

Editar: Incluso la importación manual de archivos json da como resultado el mismo error que antes. Voy a abrir una nueva edición para esto, supongo.

Abrí un nuevo número para mi problema , en caso de que alguien esté interesado en unirse a la discusión. ¡Gracias por ahora, @BrunoBernardino !

Otra opción / solución alternativa para habilitar la capacidad de usar __dirname como normalmente esperaría que se comporte es ajustar la configuración del paquete web.

De forma predeterminada, webpack creará un alias de varios nodos globales con polyfills a menos que usted le diga que no:
https://webpack.js.org/configuration/node/
Y la configuración predeterminada del paquete web es dejar __dirname y __filename solos, es decir, no rellenarlos y dejar que el nodo los maneje normalmente.

Sin embargo, la configuración del paquete web Next.js no usa / refleja los valores predeterminados del paquete web https://github.com/vercel/next.js/blob/bb6ae2648ddfb65a810edf6ff90a86201d52320c/packages/next/build/webpack-config.ts#L661 -L663

Dicho todo esto, he utilizado el siguiente complemento personalizado de configuración siguiente para ajustar la configuración del paquete web.

IMPORTANTE: esto funciona para mi caso de uso. No se ha probado en una amplia gama de entornos / configuraciones ni se ha probado con todas las pruebas unitarias / de integración de Next.js. Su uso puede tener efectos secundarios no deseados en su entorno.
Además, Next puede tener razones específicas para no usar la configuración predeterminada del paquete web para __dirname y __filename . Entonces, nuevamente, el código a continuación puede tener efectos secundarios no deseados y debe usarse con precaución.

Además, el siguiente complemento ha sido diseñado para usarse con el paquete next-compose-plugins : https://github.com/cyrilwanner/next-compose-plugins

Pero también debería funcionar como un complemento 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;
    },
  });
};

Implementé la solución con @jkjustjoshing , y aunque funciona muy bien a nivel local, no funciona cuando implemento la aplicación en Vercel.

Obtuve el siguiente error:

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

Mi código:

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

Aquí hay un enlace al archivo: https://github.com/bengrunfeld/trend-viewer/blob/master/pages/api/graphql-data.js

@bengrunfeld sí, su solución solo funciona localmente.

Últimamente tuve un problema similar (quería leer un archivo en una ruta API) y la solución fue más fácil de lo esperado.

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

@borispoehland ¡¡ Muchas gracias !! ¡Tu solución funcionó a la perfección!

@bengrunfeld no hay problema, también lo descubrí por coincidencia ( @BrunoBernardino ;)). Es la solución al problema de todos aquí, supongo.

Tenga en cuenta que aún debe configurar next.config.js . Eliminé el archivo después de ver que la solución de

Luego lo restablecí a la solución de @jkjustjoshing anterior, lo implementé nuevamente en Vercel y funcionó.

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

Tenga en cuenta que aún debe configurar next.config.js . Lo eliminé después de ver que la solución de

Lo restablecí a la solución de @jkjustjoshing anterior, lo implementé nuevamente en Vercel y funcionó.

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

@bengrunfeld ¿ PROJECT_ROOT en otro punto del código, porque en mi proyecto funciona sin él. ¿Cómo se ve el error?

Al implementar en Vercel, ¿cómo escribo un readFile en una página que funcionará tanto en SSG como en SSR / modo de vista previa?

Repositorio de demostración de dónde no funciona: https://github.com/mathdroid/blog-fs-demo

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

@mathdroid intente mover readFile y readdir dentro de las funciones getStaticProps y getStaticPaths , respectivamente; de ​​lo contrario, el código podría ejecutarse en el navegador.

Sin embargo, importar fs debería estar bien en la parte superior.

Háganos saber cómo funciona.

@borispoehland Gracias por la maravillosa solución. No esperaba que path.resolve() a /public funcionaran tanto localmente como en Vercel: ¡ojos:! Eres mi salvador del día. : +1:

@borispoehland probé su solución dentro de una función sin servidor pero aún obtengo:
ENOENT: no existe tal archivo o directorio, 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;
}

Intenté con nuestro sin el next.config.js

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

¿Quizás su solución no funciona en funciones sin servidor?

@borispoehland probé su solución dentro de una función sin servidor pero aún obtengo:
ENOENT: no existe tal archivo o directorio, 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;
}

Intenté con nuestro sin el next.config.js

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

¿Quizás su solución no funciona en funciones sin servidor?

No sé por qué no funciona por tu parte ... Lo siento

Ok, lo hice funcionar para leer usando el código @bengrunfeld pero desafortunadamente no puedes escribir:
[Error: EROFS: sistema de archivos de solo lectura, abra '/var/task/public/posts.json']
Entonces, no hay forma de actualizar un caché para evitar demasiadas llamadas a la base de datos :(

@neckaros, ¿ ha intentado usar mi enfoque para leer un archivo que no sea .json , por ejemplo, un archivo .jpg ?

Ok, lo hice funcionar para leer usando el código @bengrunfeld pero desafortunadamente no puedes escribir:

[Error: EROFS: sistema de archivos de solo lectura, abra '/var/task/public/posts.json']

Entonces, no hay forma de actualizar un caché para evitar demasiadas llamadas a la base de datos :(

@neckaros deberías poder escribir y leer desde S3 (o algún otro sistema de archivos externo), pero normalmente uso redis para cosas rápidas almacenadas en caché que pueden ser volátiles. https://redislabs.com lo mantiene "sin servidor", y tengo ejemplos de código listos para producción en https://nextjs-boilerplates.brn.sh si lo desea.

@borispoehland pude leer pero no escribir desde la función sin servicio. Pero terminé haciéndolo funcionar actualizando mi caché en las compilaciones incrementales (revalidar) en lugar de agregar contenido nuevo. Lo que supongo que no es un mal patrón. ¡Gracias por tu ayuda!

@BrunoBernardino gracias,

Realmente buscando una solución para aficionados totalmente gratuita que no se rompa una vez que tenga algunos usuarios :)

Entendido. RedisLabs y Vercel hicieron eso por mí. 💯

Después de investigar un poco, obtuve la escritura de archivos que funcionaban con el paquete del sistema operativo extendido ...

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)

leer un archivo funciona con la solución @subwaymatch
const logoPath = path.resolve('./public/logo.png')

Después de investigar un poco, obtuve la escritura de archivos que funcionaban con el paquete del sistema operativo extendido ...

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)

leer un archivo funciona con la solución @subwaymatch
const logoPath = path.resolve('./public/logo.png')

Bien, ¿puedes volver a leer el contenido de este archivo? ¿El directorio es accesible y permanente?

@marklundin Con una función llamada tmpdir dudo que sea permanente, pero si esto funciona, sería bueno saber qué tan temporal es realmente tmpdir , sí ... 🤔

¿Alguna actualización sobre esto? Me pregunto por qué funciona en getInitialProps, pero no en las rutas API 🤷‍♂️

Mi solución actual

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

actualmente también tiene este problema en las rutas API

actualmente también tiene este problema en las rutas API

Aquí hay algunas soluciones de trabajo, ¿qué problema tiene, específicamente?

Estoy luchando para que esto funcione incluso con las sugerencias de este hilo. Mi caso de uso es que estoy escribiendo una guía y quiero mostrar el código fuente del componente junto con el componente en sí. Mi método para hacer esto es usar fs para cargar el archivo jsx del componente dentro de getServerSideProps y pasar el valor de cadena del contenido del archivo como prop.

Me sentía muy feliz por tenerlo funcionando localmente, pero luego, cuando fui a implementarlo, la alegría se fue :(

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

@ElGoorf su problema es que los archivos public están en un borde y las funciones están en una lambda. Ahora, @vercel/next todavía no permite includeFiles , por lo que la forma más fácil de hacerlo funcionar sería usar una función lambda con él.

Aquí hay un código de muestra que ayudó a otros aquí: https://github.com/vercel/next.js/issues/8251#issuecomment -614220305

Gracias @BrunoBernardino No me di cuenta de que me había perdido el "x elementos ocultos cargan más ..." ¡y pensé que me estaba volviendo loco por la pérdida de significado del hilo!

Desafortunadamente, tuve problemas con su solución, ya que es la primera vez que escucho sobre Edge / Lambda, sin embargo, encontré que la solución de @balthild estaba más cerca de lo que buscaba originalmente antes de probar el método node.fs: https: / /github.com/vercel/next.js/issues/8251#issuecomment -634829189

¡Excelente! ¿Lo hiciste funcionar? ¿O sigues teniendo problemas?

No estoy seguro de que Vercel siquiera use esa terminología, pero por Edge me refiero a CDN, desde donde se sirven los archivos estáticos, y por lambda me refiero a las funciones de "backend" que se llaman desde las rutas de la API, que están aisladas como las funciones de AWS Lambda .

Oye,

¿Alguna actualización sobre la escritura en archivos usando next.js en vercel? No puedo leer ningún problema. Usando el const logoPath = path.resolve('./public/logo.png')

Estoy intentando sobrescribir el archivo public / sitemap.xml (debido a los límites de tamaño en vercel) solo puedo devolverlo sin errores como un archivo estático en la carpeta pública. Anteriormente implementé el mapa del sitio con zlib y transmití la respuesta, pero parece esperar hasta que finalice la transmisión y luego devolverla. Esto no afecta al error de limitación de tamaño, pero desafortunadamente es muy lento. Estoy abierto a cualquier sugerencia que la gente pueda tener. El mapa del sitio se crea a partir de una llamada a la API a un backend independiente y debe actualizarse periódicamente.

Cosas que he intentado:

  • Comprimir y transmitir el xml: funciona pero es muy lento.
  • Construyendo y devolviendo el mapa del sitio desde una función de API, desafortunadamente, esto alcanza el límite de tamaño.
  • Leer un archivo xml estático de una carpeta pública (funciona) independientemente del tamaño, pero no actualizable.
  • Prueba de escritura en este archivo, no funciona. Prueba de escritura en cualquier archivo / carpeta no funciona
  • Prueba que devuelve el archivo xml estático de la función "api", error de tamaño. Esto funciona a nivel local.
  • Pruebe devolver el archivo xml estático de la "página" error de tamaño getServerSideProp. Esto funciona a nivel local.
  • ¿Apreciaría alguna idea en absoluto?

Hola @emomooney , no imagino que Vercel nunca permita escribir archivos en una función (incluso para el almacenamiento en caché), ya que la principal "ventaja" de la tecnología sin servidor es su falta de estado, y eso le agregaría estado, así que creo que lo harás necesita usar el borde / cdn para ello.

Anteriormente implementé el mapa del sitio con zlib y transmití la respuesta, pero parece esperar hasta que finalice la transmisión y luego devolverla.

Tengo curiosidad por saber si solo estaba experimentando esta lentitud para las llamadas posteriores, o solo la primera, para el inicio en frío. Me imagino que esta fue una llamada API a Vercel a través de una función api next.js, o una lambda dedicada, similar a lo que hago aquí .

Si lo fue y sigue siendo demasiado lento, ¿su "backend separado" está fuera de Vercel? Si es así, potencialmente puede usarlo para construir un archivo sitemap.xml y vercel --prod en un dominio, básicamente "almacenando en caché" el archivo para que sea legible y accesible, y solo necesitaría actualizar el robots.txt para vincular el mapa del sitio a otro dominio / subdominio.

¿Fue útil esta página
0 / 5 - 0 calificaciones

Temas relacionados

havefive picture havefive  ·  3Comentarios

lixiaoyan picture lixiaoyan  ·  3Comentarios

formula349 picture formula349  ·  3Comentarios

timneutkens picture timneutkens  ·  3Comentarios

jesselee34 picture jesselee34  ·  3Comentarios