I'm using EJS to build email templates. The problem I'm having is I don't know what is the best location to keep these EJS files and any other file import for production (could be even a zip file in future).
Currently I'm keeping email templates under src/emails folder (next root is src). I performed a test with the following and everything works fine :
import type { NextApiRequest, NextApiResponse } from 'next';
import path from 'path';
const ejs = require('ejs')
type Data = {
name: string;
};
export default function handler(
req: NextApiRequest,
res: NextApiResponse<Data>
) {
const basePath = path.join(process.cwd(), 'src/emails');
ejs.renderFile(basePath + '/userVerification.ejs', { verifyURL: 'testurl' }, (err: {}, data: any) => {
console.log(data);
res.status(200).end('test');
});
}
But I noticed NextJS is getting the file from src folder which won't be available on server. I thought maybe I can put them into src/public folder since it'll be exported but I couldn't make sure.
My question is where is the best location for keeping these files?
After performing a few tests, I noticed that api can actually access to anything within project folder. Only public folder content can be accessed publicly by URL. That being said folder location for custom files can be anywhere.
.next
public
server_files *(all files can be imported by API)*
--ejs (templates email templates)
--pdf (generated invoice etc.)
--zip
src
--components
--pages
Related
while working on the local host, api and the posts.jaon file also works fine. was able to perform CRUD.But after I deploy it to vercel, the api does not loads.
error in the log is something like this:
[GET] /api/insta 11:39:32:83 [Error: ENOENT: no such file or directory, open './posts.json'] { errno: -2, code: 'ENOENT', syscall: 'open', path: './posts.json' }
expecting a json response in the browser when I hit the api.
the json file is in the pages/api folder of next app.
I tried moving the json file outside pages at the top level of the folder strecture, and changing the path inside the fs("file.json",....). but nothing worked
This article from Vercel might help you:
How to Load Data from a File in Next.js
Here is an excerpt:
import path from 'path';
import { promises as fs } from 'fs';
export default async function handler(req, res) {
const jsonDirectory = path.join(process.cwd(), 'json');
const fileContents = await fs.readFile(jsonDirectory + '/data.json', 'utf8');
res.status(200).json(fileContents);
}
You would then have a folder named json where all the data is saved. That is declared in line 4. If you want to rename it, it is of course possible.
I'm trying to resolve a file path in NextJS.
I understand that API routes are working a little bit differently when deployed to Vercel. In order to create a correct path to the file I assumed I had to do this:
const svg = fs.readFileSync(
path.join(process.cwd(), "img", "file.svg"),
"utf-8",
);
// ENOENT: no such file or directory
But I cannot make it work. The file cannot be found under that path.
How can I find the correct path for a file in NextJS api routes?
I've followed the documentation of this.
Next version is: 11.1.3
When logging the path, it is giving /var/task/packages/project-root/img/file.svg
Try using
path.resolve("img", "file.svg")
Maybe it should help.
Pretty sure you'll find the file if you serve it as a static file - Next.js documentation here
I'm thinking it's not bundled in the deployment, but whatever you have in /public will definitely be deployed.
Good luck 💪🏻
I manage to create a small sandbox that will clarify your issue. Open it using StackBlitz
Project Structure
.
├── pages
| ├── api
| | ├── hello.js
| ├── _app.js
| ├── index.js
├── public
| ├── 1.txt --> this is a demonstration file
I reproduce your code in the hello api for testing purposes
const { readFileSync } = require('fs');
const { join } = require('path');
export default (req, res) => {
const path = join(process.cwd(), '/public/1.txt');
const value = readFileSync(path, { encoding: 'utf-8' });
res.status(200).json({ value });
};
This API entry is called from the index.js file
import Head from 'next/head';
import { useEffect, useState } from 'react';
export default function Home() {
const [value, setValue ] = useState('');
useEffect(() => {
fetch('/api/hello')
.then((res) => res.json())
.then(data => setValue(data.value));
});
return (
<div>
<Head>
<title>Create Next App</title>
</Head>
<main>
<h1>{value}</h1>
</main>
</div>
);
}
Yes, this is a very simplified version (for testing purposes only.. I assume we won't use readFileSync in production) - but - it reproduces your code.
Unfortunately, it works perfectly fine in dev mode and in production mode (npm run build + npm start), which means:
You either misconfigured your img folder
Perhaps you are lacking read permissions for the path you are using. For instance if you deploy your work to a remote machine, most directories will have limited access and therefore prevent you from reading the file (for testing this theory please read this post and execute it on your deployed machine)
For anyone coming across this, I actually opened a ticket at Vercel to ask them about this.
It turns out it was a caching issue that is caused by using Yarn 3
The support redirected me to this page explaining that they would have issues with anything above Yarn 1.
According to them there is nothing really they can do about right now but suggest us to use a different package manager.
I'm using Yarn 1.22, but still have this issue. The reason is because files are not generated during build and run times, so they are never found. The way to get around this is to create a separate .js file that to wrap around the said static files (html, txt, etc). Export this JS object which contains the files, and Vercel will generate them. I'm using this to generate email templates.
//account_verify.js
import path from 'path';
import { promises as fs } from 'fs';
import { prefixPath } from './constants';
// TODO: force this to conform to a typescript type
export default {
subject: 'Confirm Your Account',
data: {
email_verification_link: '{{email_verification_link}}',
first_name: '{{first_name}}'
},
templates: {
txt: fs.readFile(path.join(process.cwd(), prefixPath, 'account_verify.txt'), 'utf8'),
html: fs.readFile(path.join(process.cwd(), prefixPath, 'account_verify.html'), 'utf8'),
}
};
I'm trying to setup my nextjs app to use runtime configurations. Basically, I have an endpoint url that needs to be available trough docker env vars.
I configured following these docs but it isn't working. My app still using default values from .env file. Could anyone help to understand what I missed or did wrong?
Thanks!
docs:
https://nextjs.org/docs/api-reference/next.config.js/runtime-configuration
https://nextjs.org/docs/advanced-features/custom-app
steps:
1- added to my next.config.js
publicRuntimeConfig: {
NEXT_PUBLIC_BACKEND_HOST: process.env.NEXT_PUBLIC_BACKEND_HOST,
},
2- retrieved config in my pages
const { publicRuntimeConfig } = getConfig()
const baseURL = publicRuntimeConfig.NEXT_PUBLIC_BACKEND_HOST
3- created a custom app to setup getInitialProps
Runtime configuration won't be available to any page (or component in a page) without getInitialProps.
import App from 'next/app'
function MyApp({ Component, pageProps }) {
return <Component {...pageProps} />
}
MyApp.getInitialProps = async (appContext) => {
const appProps = await App.getInitialProps(appContext);
return { ...appProps }
}
export default MyApp
Everything seems fine in your code, tested in a fresh project and everything worked correctly. Therefore I think the issue is that you don't actually have NEXT_PUBLIC_BACKEND_HOST env var set when you're running next start. Btw, you don't need to use the NEXT_PUBLIC prefix in this kind of usage. If you want build time args you can use NEXT_PUBLIC_ prefix to have the var be available both client and server side by just using process.env.NEXT_PUBLIC_ anywhere. Note that in that case the value will be inlined at build time, so the env var needs to be present during build.
Project:
I am working on an E-commerce application and it has more than 1,600 products and 156 categories.
Problem:
Initially, on the first product page, 30 products will be fetched (due to the page limitation), but on the left sidebar, I need filters that will be decided on the basis of tags of all 1,600 products. So that's why I need all the products in the first fetch and then I will extract common tags by looping over all the products and immediately show them on the sidebar.
What do I want?
I am not sure but I think it would be the best solution if I generate a JSON file containing all the products and store it somewhere, where I can fetch just hitting the URL using REST API in Next JS (either in getServerSideProps or getStaticProps).
Caveat:
I tried by storing JSON file in ./public directory in next js application, it worked in localhost but not in vercel.
Here is the code I wrote for storing JSON file in ./public directory:
fs.writeFileSync("./public/products.json", JSON.stringify(products, null, 2)); //all 1,600 products
One solution it to fetch it directly from front-end (if the file is not too big) otherwise, for reading the file in getServerSideProps you will need a custom webpack configuration.
//next.config.js
const path = require("path")
const CopyPlugin = require("copy-webpack-plugin")
module.exports = {
target: "serverless",
future: {
webpack5: true,
},
webpack: function (config, { dev, isServer }) {
// Fixes npm packages that depend on `fs` module
if (!isServer) {
config.resolve.fallback.fs = false
}
// copy files you're interested in
if (!dev) {
config.plugins.push(
new CopyPlugin({
patterns: [{ from: "content", to: "content" }],
})
)
}
return config
},
}
Then you can create a utility function to get the file:
export async function getStaticFile(file) {
let basePath = process.cwd()
if (process.env.NODE_ENV === "production") {
basePath = path.join(process.cwd(), ".next/server/chunks")
}
const filePath = path.join(basePath, `file`)
const fileContent = await fs.readFile(filePath, "utf8")
return fileContent
}
There is an open issue regarding this:
Next.js API routes (and pages) should support reading files
Following Meteor docs on how to use the import directory structure, Example directory layout.
//-------------- publication.js`
import {Vehicles} from '../vehicles.js';
Meteor.publish('vehicles', function () {
return Vehicles.find();
});
//-------------- carClass.jsx
import './vehicles/server/publications.js';
const composer = (props, onData) => {
const subscription = Meteor.subscribe('vehicles');
if (subscription.ready()) {
const vehicle = Vehicles.findOne({name: 'jack'});
onData(null, { vehicle });
}
};
Does the publish method need to be exported?
Error in browser console saying:
Uncaught Error: Cannot find module './vehicles/server/publications.js'
How can this error be fixed? Thanks
Meteor publications are server-only code, so you can't import that script in carClass.jsx.
You should have some file like {app root}/server/main.js. You import your publications here to make them available for client scripts to subscribe to. It's important that this file isn't inside of the /imports folder, so that it is eagerly loaded when the server starts.
The problem is that the path ./vehicles/server/publications.js is not reachable from the carClass.jsx file. You should reference it by ./server/publications.js