NextJS environment variable undefined in API route - reactjs

I'm using NextJS v 10.0.9. I have created an .env.development.local file in the root of my project, as described in the docs. Contents:
API_SERVER=http://127.0.0.1:5000/map/abc123
In an API route:
pages/api/objects.js
export function getObjects() {
console.log(process.env.API_SERVER)
}
But when I run the application with next dev, this prints undefined. I have restarted my server numerous times since defining/changing the variable, but always get undefined. I've also tried adding a NEXT_PUBLIC_ prefix, which shouldn't be necessary and isn't desired, but I wanted to see what would happen. Result: no change. I've also tried using .env.local.
What am I doing wrong?
Update: I found this line in the docs on API routes: "For an API route to work, you need to export a function as default (a.k.a request handler), which then receives the following parameters: req, res."
So I've modified the code in my API route to:
export default function getObjects(req, res) {
console.log("test ", process.env.API_SERVER)
}
But it still isn't working. I do not have a next.config.js file because, as I understand it, this is no longer necessary as of NextJS 9.4.
I have also tried declaring and using an entirely new variable in the file (TEST=value), but this is also undefined when I try to use it in the API route.

The issue was how I was calling the API route. When I visited it directly in the browser, the environment variable was available and printed in the console just fine. But I had been following a tutorial that wasn't written for NextJS, and it had me import the API functions directly:
// WRONG WRONG WRONG
import getObjects from './api/objects'
// [...]
export default function MyApp({}) {
useEffect(() => {
getObjects().then(data => {
// do stuff
}
})
}

Related

session.subscribe throws error when called in onMount

<script>
import {onMount} from 'svelte';
import {session} from "$app/stores"
import {writable} from 'svelte/store';
const store = writable('some value');
let value = null
onMount(() => {
// this works
// return store.subscribe( (storeValue) => {value = storeValue}); // this works
// this throws an error:
// return session.subscribe( (sessionValue) => {value = sessionValue});
// Uncaught (in promise) Error: Function called outside component initialization
});
</script>
can someone please explain to me the problem with session.subscribe and why it keeps throwing?
if I move session.subscribe outside onMount it runs fine.
Note: this code is part of a SvelteKit Project, inside a Svelte component, not a SvelteKit page/route.
What goes wrong
It seems that you are actually experiencing intended behaviour. Under the documentation for $app/stores you will find this:
Stores are contextual — they are added to the context of your root component. This means that session and page are unique to each request on the server, rather than shared between multiple requests handled by the same server simultaneously, which is what makes it safe to include user-specific data in session.
Because of that, you must subscribe to the stores during component initialization (which happens automatically if you reference the store value, e.g. as $page, in a component) before you can use them.
When you were attempting this, you probably got a callstack that looks something like this:
Error: Function called outside component initialization
at get_current_component (index.mjs:953:15)
at getContext (index.mjs:989:12) <----------Here is the problem
at getStores (stores.js:19:17)
at Object.subscribe (stores.js:70:17)
at index.svelte:10:13
at run (index.mjs:18:12)
at Array.map (<anonymous>)
at index.mjs:1816:45
at flush (index.mjs:1075:17)
at init (index.mjs:1908:9)
We can see that Svelte attempts to call getContext when you subscribe to the session. Calling getContext outside of the component root is not allowed, which causes the subscription to fail.
I agree that this is quite unintuitive and I am not really sure why they implemented it this way.
Workaround
By the way, are you really sure you only want to subscribe to session on mount? What are you trying to do?
If you really only want to subscribe to session after component mount, you could use this workaround: Create your own store that updates whenever the session changes, then listen to that.
<script>
import { onMount } from "svelte";
import { session } from "$app/stores";
import { writable } from "svelte/store";
let mySession = writable($session);
$: $mySession = $session;
onMount(()=>{
mySession.subscribe(...whatever...);
})
</script>

How to test custom React-query hook that is based on Firebase methods?

I came across react-query-firebase which are hooks that are built on React-query for firebase.
I also found library called mock service worker https://mswjs.io/ however it is based on REST and GraphQL.
Here is an example code how I would use these hooks:
import React from "react";
import { useFirestoreDocument } from "#react-query-firebase/firestore";
import {
doc
} from "firebase/firestore";
import { firestore } from "../firebase";
function GetUser() {
const id = "pW5CizOJOpXezr5lGGshDmKdVpP3";
const ref = doc(firestore, "users", id);
const user = useFirestoreDocument(["users", id], ref);
return (
<div>
{user.isLoading && <div>Loading...</div>}
{user.data && <div>{user.data.data()?.name}</div>}
</div>
);
}
export default GetUser;
I am new to testing and I have no idea how would I have to execute this test, since I am mocking requests can I use random url anyways or does it have to be firebase related methods?
react-query-firebase library is an abstraction over Firebase that encapsulates resource paths (it's an SDK, I believe). Since the paths (URLs) are abstracted from, you have at least two options for how to mock requests issued by such libraries.
Option 1: Use explicit paths
Although the exact resource paths are hidden away, requests still reference existing absolute paths. You can observe those in the "Network" tab of your browser or by enabling a simple request introspection with msw:
// my.test.js
import { setupServer } from 'msw/node'
const server = setupServer(/* no handlers */)
beforeAll(() => server.listen())
afterAll(() => server.close())
Since we're using setupServer with no handlers, all requests that happen in my.test.js will be printed as warnings to stderr. You can observe those warnings to see what resource paths your SDK requests.
The benefit of using an SDK is that it guarantees you a certain resource path structure. Most likely, you will be able to replicate that structure in your mocks:
// src/mocks.js
import { rest } from 'msw'
export const handlers = [
rest.get('https://some-resource.:checksum.firebase.app/path', (req, res, ctx) => res(ctx.text('hello)))
]
Utilize dynamic path segments like :checksum to match a broader range of paths.
The downside of this approach is that your mock definition becomes dependent on the SDK internal details (resource paths). Any updates to the SDK may break your mocks, since they may update the path structure internally without denoting it as a breaking change (abstracted paths are not public API).
Option 2: Spy on the SDK
Alternatively, you can spy on the SDK you're using. An example of such a spy would be using jest.spyOn on the react-query-firebase directly:
// my.test.js
import * as reactQueryFirebase from 'react-query-firebase'
it('creates a user', () => {
jest.spyOn(reactQueryFirebase, 'SOME_METHOD_NAME')
.mockReturnValue(/* mock */)
})
The downside of this method is that you're stubbing the functions from a third-party library, which means your code under test never calls those functions. This decreases the reliability of such a test, and you should, generally, avoid resorting to this approach.
I do recommend you research Firebase, since major SDK providers often have guidelines on mocking their API. Often such a guidance would include using a dedicated third-party package authored by the SDK provider that allows mocking their internal resources.

getInitialProps causing ERR_TOO_MANY_REDIRECTS error on my live site, but not on my local version

So, I've been trying to implement cookies on my website, to keep track of a list of JavaScript objects, so the page stays consistent when the user comes back to it. I've been following this tutorial here.
On my local machine, using npm run dev on localhost:3000, it works absolutely perfect. However, when I push the commit to GitHub, it builds on Vercel without any issue, but when I try and access the live website on the internet, it gives me a 'ERR_TOO_MANY_REDIRECTS' error.
I'm pretty confused as to why it would work perfectly fine on my locally hosted site, but freaks out and does not work when it's put into production.
I think I have narrowed the problem down to getInitialProps because when I comment out the implementation in my index.js file, it still doesn't work, but when I comment out getInitialProps, it works again.
Here is the code I think may be the problem.
Home.getInitialProps = async ({req, res}) => {
const data = parseCookies(req)
if (res) {
if (Object.keys(data).length === 0 && data.constructor === Object) {
res.writeHead(301, { Location: "/" })
res.end()
}
}
return {
data: data && data,
}
}
And here is the code for that parseCookies method, which is imported as
import { parseCookies } from "../helpers/index"
within my index.js
import cookie from "cookie"
export function parseCookies(req) {
return cookie.parse(req ? req.headers.cookie || "" : document.cookie)
}
I'm super confused at this point, I've walked myself through the code a dozen times now and still have no idea what I might be doing wrong. Any help would be much appreciated! And please lemme know if there's anymore info I can provide!
The ERR_TOO_MANY_REDIRECTS error occurs because Object.keys(data).length === 0 && data.constructor === Object returns true when no cookies are set and you access the homepage. When this happens the redirect takes you back to / (the homepage) which then makes the check again and a new redirect occurs, and so on.
Locally, you probably have cookies set, so you don't experience the issue. However, when you access the website hosted on Vercel, no cookies are present initially, which triggers the infinite redirect cycle.
To fix the issue simply remove the logic from the homepage, since that's the redirect destination. You can still have it on other pages and redirect to the homepage, though.

Authentication to serve static files on Next.js?

So, I looked for a few authentication options for Next.js that wouldn't require any work on the server side of things. My goal was to block users from entering the website without a password.
I've set up a few tests with NextAuth (after a few other tries) and apparently I can block pages with sessions and cookies, but after a few hours of research I still can't find how I would go about blocking assets (e.g. /image.png from the /public folder) from non-authenticated requests.
Is that even possible without a custom server? Am I missing some core understanding here?
Thanks in advance.
I did stumble upon this problem too. It took my dumbass a while but i figured it out in the end.
As you said - for auth you can just use whatever. Such as NextAuth.
And for file serving: I setup new api endpoint and used NodeJS magic of getting the file and serving it in pipe. It's pretty similar to what you would do in Express. Don't forget to setup proper head info in your response.
Here is little snippet to demonstrate (typescript version):
import { NextApiRequest, NextApiResponse } from 'next'
import {stat} from "fs/promises"
import {createReadStream, existsSync} from "fs"
import path from "path"
import mime from "mime"
//basic nextjs api
export default async function getFile (req: NextApiRequest, res: NextApiResponse) {
// Dont forget to auth first!1!!!
// for this i created folder in root folder (at same level as normal nextjs "public" folder) and the "somefile.png" is in it
const someFilePath = path.resolve('./private/somefile.png');
// if file is not located in specified folder then stop and end with 404
if (! existsSync(someFilePath)) return res.status(404);
// Create read stream from path and now its ready to serve to client
const file = createReadStream(path.resolve('./private/somefile.png'))
// set cache so its proper cached. not necessary
// 'private' part means that it should be cached by an invidual(= is intended for single user) and not by single cache. More about in https://stackoverflow.com/questions/12908766/what-is-cache-control-private#answer-49637255
res.setHeader('Cache-Control', `private, max-age=5000`);
// set size header so browser knows how large the file really is
// im using native fs/promise#stat here since theres nothing special about it. no need to be using external pckages
const stats = await stat(someFilePath);
res.setHeader('Content-Length', stats.size);
// set mime type. in case a browser cant really determine what file its gettin
// you can get mime type by lot if varieties of methods but this working so yay
const mimetype = mime.getType(someFilePath);
res.setHeader('Content-type', mimetype);
// Pipe it to the client - with "res" that has been given
file.pipe(res);
}
Cheers

auth0 parseHash can't create property '__enableIdPInitiatedLogin' on hash string

I'm trying to upgrade my React web app from auth0-js 9.6.1 to 9.7.3. After installing the new library, my Slack login flow no longer works, it appears to be breaking in the callback.
TypeError: Cannot create property '__enableIdPInitiatedLogin' on string '#access_token={token string}&token_type=Bearer&state={state string}'
My parseHash call is:
this.auth0.parseHash(hash, (err, authResult) => {
if (authResult && authResult.idToken) {
AuthService.setToken(authResult.idToken); // JWT returned from Auth0;
// Redirect user to content.
const returnUrl = localStorage.getItem(Variables.RETURN_URL_KEY);
localStorage.removeItem(Variables.RETURN_URL_KEY);
returnUrl
? window.location.replace(returnUrl)
: window.location.replace("/");
} else if (err) {
console.log("Error with auth callback", err);
window.location.replace("https://foo.com"); // If auth fails, send user to home page.
}
}
This works fine in 9.6.1, but fails in 9.7.x and I can't find anything about any breaking changes that would cause it to start failing. Any ideas?
I had the same issue as you so I opened a ticket on the Auth0.js library github page.
This is the response I got from the developers:
It was working by accident then (also, the string is being ignored in your case), considering that we expect the first parameter to either be an object or a callback function.
All of our docs mention that:
https://github.com/auth0/auth0.js#api
https://auth0.github.io/auth0.js/global.html#parseHash
https://auth0.com/docs/libraries/auth0js/v9#extract-the-authresult-and-get-user-info
In your case, the simplest fix is to just remove the first parameter and keep only the callback. window.location.hash is already used when there's no options object.
(emphasis on the fix mine)
I tested 9.7.3 with this.auth.auth0.parseHash((err, result) => ... and it worked like a charm.
I hope this'll help!

Resources