Actualmente no es posible leer archivos de rutas o páginas API.
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.
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).
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. 😄
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
src
?)@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:
next export
?getStaticPaths
devuelve una lista de parámetros de ruta, que luego (a continuación) se alimenta, uno por uno, en getStaticProps
para cada render?getStaticProps
sin getStaticPaths
, por ejemplo, para una página sin ningún parámetro?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:
__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 noNextApiRequest
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';
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 deLo 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
@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:
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.
Comentario más útil
Solución alternativa que estoy usando:
y en el lugar necesitas el camino
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
).