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.
Related
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'),
}
};
import nextJest from 'next/jest'
const createJestConfig = nextJest({
dir: './',
})
// Add any custom config to be passed to Jest
const customJestConfig = {
setupFilesAfterEnv: ['<rootDir>/setupTests.js'],
moduleDirectories: ['node_modules', '<rootDir>/'],
testEnvironment: 'jest-environment-jsdom',
modulePathIgnorePatterns: ['./cypress'],
// testMatch: ['<rootDir>/**/*.test.js', '<rootDir>/**/*.test.jsx'],
}
module.exports = createJestConfig(customJestConfig)
In my project, we use Nextjs application with both Cypress and Jest. The latest jest.config.ts which is recommended is shown above.
If you are now owned this problem. you can maybe try to check your modulePathIgnorePatterns.
I added a ./ to ['cypress'], then it works well. So, I think maybe it just cann't recognize the path.
Tried this link and created my first store in Quasar using Pinia, I also needed to change the .quasar/app.js manually to add the Pinia store and to make Pinia functional.
import { Quasar } from 'quasar'
import { markRaw } from 'vue'
import RootComponent from 'app/src/App.vue'
import createStore from 'app/src/stores/index'
import createRouter from 'app/src/router/index'
export default async function (createAppFn, quasarUserOptions) {
// Create the app instance.
// Here we inject into it the Quasar UI, the router & possibly the store.
const app = createAppFn(RootComponent)
app.config.devtools = true
app.use(Quasar, quasarUserOptions)
const store = typeof createStore === 'function'
? await createStore({})
: createStore
app.use(store)
const router = markRaw(
typeof createRouter === 'function'
? await createRouter({store})
: createRouter
)
// make router instance available in store
store.use(({ store }) => { store.router = router })
// Expose the app, the router and the store.
// Note that we are not mounting the app here, since bootstrapping will be
// different depending on whether we are in a browser or on the server.
return {
app,
store,
router
}
}
But the problem is .quasar/app.js is re-written with default contents as soon as quasar dev is executed and again I don't have access to the Pinia stores anymore.
As I said this application was based on vuex formerly.
Make sure you have the index file for pinia.
In "src/stores/index.js"
import { store } from 'quasar/wrappers'
import { createPinia } from 'pinia'
/*
* If not building with SSR mode, you can
* directly export the Store instantiation;
*
* The function below can be async too; either use
* async/await or return a Promise which resolves
* with the Store instance.
*/
export default store((/* { ssrContext } */) => {
const pinia = createPinia()
// You can add Pinia plugins here
// pinia.use(SomePiniaPlugin)
return pinia
})
Try checking quasar info
quasar info
Notice #quasar/app-webpack and vuex.
If you are using #quasar/app, try to move to #quasar/app-webpack by upgrading quasar.
quasar upgrade -i
If you have vuex installed in your quasar info output, try to remove it.
npm uninstall vuex
In your package-lock.json, look for "node_modules/vuex" and delete the key and value.
Then delete your "node_modules" folder and run npm i
After that, run quasar clean.
You may try creating a Pinia store via quasar command to validate it.
quasar new store <store_name>
It should generate a pinia store instead of vuex store.
Problem is older version of #quasar/app-webpack package. It got support for Pinia since v3.4.0. Check release notes here. So basically upgrade this package.
Run quasar upgrade -i and then quasar new store <store_name> [--format ts]
It will create a stores/ directory with pinia.
In my case i didn't need to edit any special files, simply replace the index.js in the stores folder. To get quasar CLI to then use pinia when running quasar new store I had to use quasar clean and just like that I had fully transitioned.
My solution was to remove and reinstall node_modules
My Main goal is to create an Electron App (Windows) that locally stores data in an SQLite Database. And because of type safety I choose to use the Prisma framework instead of other SQLite Frameworks.
I took this Electron Sample Project and now try to include Prisma. Depending on what I try different problems do arrise.
1. PrismaClient is unable to be run in the Browser
I executed npx prisma generate and then try to execute this function via a button:
import { PrismaClient } from '#prisma/client';
onSqlTestAction(): void {
const prisma = new PrismaClient();
const newTestObject = prisma.testTable.create(
{
data: {
value: "TestValue"
}
}
);
}
When executing this in Electron I get this:
core.js:6456 ERROR Error: PrismaClient is unable to be run in the browser.
In case this error is unexpected for you, please report it in https://github.com/prisma/prisma/issues
at new PrismaClient (index-browser.js:93)
at HomeComponent.onSqlTestAction (home.component.ts:19)
at HomeComponent_Template_button_click_7_listener (template.html:7)
at executeListenerWithErrorHandling (core.js:15281)
at wrapListenerIn_markDirtyAndPreventDefault (core.js:15319)
at HTMLButtonElement.<anonymous> (platform-browser.js:568)
at ZoneDelegate.invokeTask (zone.js:406)
at Object.onInvokeTask (core.js:28666)
at ZoneDelegate.invokeTask (zone.js:405)
at Zone.runTask (zone.js:178)
It somehow seems logical that Prisma cannot run in a browser. But I actually build a native app - with Electron that embeds a Browser. It seems to be a loophole.
2. BREAKING CHANGE: webpack < 5 used to include polyfills
So i found this Question: How to use Prisma with Electron
Seemed to be exactly what I looked for. But the error message is different (Debian binaries were not found).
The solution provided is to generate the prisma artifacts into the src folder instead of node_modules - and this leads to 19 polyfills errors. One for example:
./src/database/generated/index.js:20:11-26 - Error: Module not found: Error: Can't resolve 'path' in '[PATH_TO_MY_PROJECT]\src\database\generated'
BREAKING CHANGE: webpack < 5 used to include polyfills for node.js core modules by default.
This is no longer the case. Verify if you need this module and configure a polyfill for it.
If you want to include a polyfill, you need to:
- add a fallback 'resolve.fallback: { "path": require.resolve("path-browserify") }'
- install 'path-browserify'
If you don't want to include a polyfill, you can use an empty module like this:
resolve.fallback: { "path": false }
And this repeats with 18 other modules. Since the error message to begin with was different I also doubt that this is the way to go.
I finally figured this out. What I needed to understand was, that all Electron apps consist of 2 parts: The Frontend Webapp (running in embedded Chromium) and a Node backend server. Those 2 parts are called IPC Main and IPC Renderer and they can communicate with each other. And since Prisma can only run on the main process which is the backend I had to send my SQL actions to the Electron backend and execute them there.
My minimal example
In the frontend (I use Angular)
// This refers to the node_modules folder of the Electron Backend, the folder where the main.ts file is located.
// I just use this import so that I can use the prisma generated classes for type safety.
import { TestTable } from '../../../app/node_modules/.prisma/client';
// Button action
onSqlTestAction(): void {
this.electronService.ipcRenderer.invoke("prisma-channel", 'Test input').then((value) => {
const testObject: TestTable = JSON.parse(value);
console.log(testObject);
});
The sample project I used already had this service to provide the IPC Renderer:
#Injectable({
providedIn: 'root'
})
export class ElectronService {
ipcRenderer: typeof ipcRenderer;
webFrame: typeof webFrame;
remote: typeof remote;
childProcess: typeof childProcess;
fs: typeof fs;
get isElectron(): boolean {
return !!(window && window.process && window.process.type);
}
constructor() {
// Conditional imports
if (this.isElectron) {
this.ipcRenderer = window.require('electron').ipcRenderer;
this.webFrame = window.require('electron').webFrame;
this.childProcess = window.require('child_process');
this.fs = window.require('fs');
// If you want to use a NodeJS 3rd party deps in Renderer process (like #electron/remote),
// it must be declared in dependencies of both package.json (in root and app folders)
// If you want to use remote object in renderer process, please set enableRemoteModule to true in main.ts
this.remote = window.require('#electron/remote');
}
}
And then in the Electron backend I first added "#prisma/client": "^3.0.1" to the package.json (for the Electron backend not the frontend). Then I added to the main.ts this function to handle the requests from the renderer:
// main.ts
ipcMain.handle("prisma-channel", async (event, args) => {
const prisma = new PrismaClient();
await prisma.testTable.create(
{
data: {
value: args
}
}
);
const readValue = await prisma.testTable.findMany();
return JSON.stringify(readValue);
})
This way of simply adding the IPC Main handler in the main.ts file of course is a big code smell but usefull as minimal example. I think I will move on with the achitecture concept presented in this article.
I have an issue using cordova-sqlite adapter in PouchDB. It works perfect locally (I can create, update and delete docs) but is not able to sync from/to any remote CouchDB server. I tried both IBM Cloudant and a CouchDB server of my own.
If I use IDB adapter instead it works like a charm, but when I change to SQLite is when it cannot sync properly.
This is how I use it (in my case inside a Vue app):
import Vue from 'vue'
import App from './App.vue'
import router from './router'
import store from './store'
import PouchDB from 'pouchdb'
...
Object.defineProperty(Vue.prototype, '$pouch', { value: PouchDB }); //this is to be able to use $pouch in any Vue component later on
...
var PouchAdapterCordovaSqlite = require('pouchdb-adapter-cordova-sqlite');
PouchAdapterCordovaSqlite.use_prefix = true; // use the legacy '_pouch' prefix. I tried both with and without this option
PouchDB.plugin(PouchAdapterCordovaSqlite);
...
this.db = new this.$pouch('todos', { adapter: 'cordova-sqlite', location: 'default', androidDatabaseImplementation: 2 });
console.log('PouchDB adapter: ' + this.db.adapter); //it returns 'cordova-sqlite'
let remoteCouch='http://myserveraddress.com:5984/todos'; //todos is the remote database, with CORS enabled and set as public so no user/pass is needed
this.$pouch.sync('todos', remoteCouch, {
live: true,
retry: true
}).on('change', function (info) { console.log('change:' + info}).on('paused', function (err) {//...//}).on --- and so on with 'active', 'denied', 'complete' and 'error'
and later in one Vue component I use this (where this.db refers to the database and this.todos is used to show results on screen:
mounted() {
this.list();
this.db
.changes({
since: "now",
live: true
})
.on("change", this.list);
},
methods: {
list: function() {
let self = this;
this.db.allDocs({ include_docs: true, descending: true }, function(
err,
doc
) {
self.todos = doc.rows;
});
}
}
As I mentioned before it works with IndexedDB but not with SQLite adapter (I used both cordova-sqlite-storage and cordova-plugin-sqlite-2 with same results). Everything starts after deviceready event and sqlite is loaded properly (I am able to use window.sqlitePlugin).
And when it comes to Cordova config.xml I ensured to define this:
<plugin name="cordova-plugin-whitelist"/>
<access origin="*"/>
<allow-intent href="http://*/*"/>
<allow-intent href="https://*/*"/>
...
<plugin name="cordova-plugin-sqlite-2" spec="*"/>
Any clue? I really need to use SQLite instead of IDB because my app will store big amount of data, and I also need an offline-first approach, that's why I use PouchDB.
Another test that I tried was to create a SQLite database manually from my app and it also works, so it seems that the problem is related to pouchdb-adapter-cordova-sqlite.
Screenshot with IDB:
result with idb
And screenshot with SQLite:
result with sqlite
Thanks in advance!
Solved!
The problem was here:
this.$pouch.sync('todos', remoteCouch, {...
It has to be changed to:
this.db.sync(remoteCouch, {...
So it was pointing to pouchdb object instead of the database instance.
I hope it helps someone.
Regards!