I am trying to implement route protection using the new Next.js 12 middleware function. But it occurs to me that every time I try to access the session I get null. Hence not getting the expected result. Why is that?
import { NextResponse, NextRequest } from 'next/server'
import { getSession } from "next-auth/react"
//This acts like a middleware, will run every time for pages under /dashboard, also runs for the nested pages
const redirectUnauthenticatedUserMiddleware = async (req, ev) => {
const session = await getSession({ req })
if (!session) {
return NextResponse.redirect('/login')
}
return NextResponse.next()
}
export default redirectUnauthenticatedUserMiddleware
There's another ways on how you can get access to your session.
Here we're using getToken from next-auth/jwt.
to note:
must use or declared secret as an option in NextAuth function (related reference here). If not, it will not work. Doesn't matter if you use session or jwt option. Both will work.
export default NextAuth({
secret: process.env.SECRET,
...otherOptions
})
currently I don't know whether it will work or not if you don't use token. Or you use database option only. Or adapter. I'm not sure since currently I'm unable to make adapter work just yet. So that's that.
Here is an example _middleware.js:-
import { getToken } from 'next-auth/jwt';
import { NextResponse } from 'next/server';
export async function middleware(req, ev) {
// 'secret' should be the same 'process.env.SECRET' use in NextAuth function
const session = await getToken({ req: req, secret: process.env.SECRET }); console.log('session in middleware: ', session)
if(!session) return NextResponse.redirect('/')
return NextResponse.next()
}
If you already found a solution on how to make use getSession. You may upload your answer and share with us. Thank you.
Related
I'm new to Next.js and I'm trying to understand the suggested structure and dealing with data between pages or components.
For instance, inside my page home.js, I fetch an internal API called /api/user.js which returns some user data from MongoDB. I am doing this by using fetch() to call the API route from within getServerSideProps(), which passes various props to the page after some calculations.
From my understanding, this is good for SEO, since props get fetched/modified server-side and the page gets them ready to render. But then I read in the Next.js documentation that you should not use fetch() to all an API route in getServerSideProps(). So what am I suppose to do to comply to good practice and good SEO?
The reason I'm not doing the required calculations for home.js in the API route itself is that I need more generic data from this API route, as I will use it in other pages as well.
I also have to consider caching, which client-side is very straightforward using SWR to fetch an internal API, but server-side I'm not yet sure how to achieve it.
home.js:
export default function Page({ prop1, prop2, prop3 }) {
// render etc.
}
export async function getServerSideProps(context) {
const session = await getSession(context)
let data = null
var aArray = [], bArray = [], cArray = []
const { db } = await connectToDatabase()
function shuffle(array) {
var currentIndex = array.length, temporaryValue, randomIndex;
while (0 !== currentIndex) {
randomIndex = Math.floor(Math.random() * currentIndex);
currentIndex -= 1;
temporaryValue = array[currentIndex];
array[currentIndex] = array[randomIndex];
array[randomIndex] = temporaryValue;
}
return array;
}
if (session) {
const hostname = process.env.NEXT_PUBLIC_SITE_URL
const options = { headers: { cookie: context.req.headers.cookie } }
const res = await fetch(`${hostname}/api/user`, options)
const json = await res.json()
if (json.data) { data = json.data }
// do some math with data ...
// connect to MongoDB and do some comparisons, etc.
But then I read in the Next.js documentation that you should not use fetch() to all an API route in getServerSideProps().
You want to use the logic that's in your API route directly in getServerSideProps, rather than calling your internal API. That's because getServerSideProps runs on the server just like the API routes (making a request from the server to the server itself would be pointless). You can read from the filesystem or access a database directly from getServerSideProps. Note that this only applies to calls to internal API routes - it's perfectly fine to call external APIs from getServerSideProps.
From Next.js getServerSideProps documentation:
It can be tempting to reach for an API Route when you want to fetch
data from the server, then call that API route from
getServerSideProps. This is an unnecessary and inefficient approach,
as it will cause an extra request to be made due to both
getServerSideProps and API Routes running on the server.
(...) Instead, directly import the logic used inside your API Route
into getServerSideProps. This could mean calling a CMS, database, or
other API directly from inside getServerSideProps.
(Note that the same applies when using getStaticProps/getStaticPaths methods)
Here's a small refactor example that allows you to have logic from an API route reused in getServerSideProps.
Let's assume you have this simple API route.
// pages/api/user
export default async function handler(req, res) {
// Using a fetch here but could be any async operation to an external source
const response = await fetch(/* external API endpoint */)
const jsonData = await response.json()
res.status(200).json(jsonData)
}
You can extract the fetching logic to a separate function (can still keep it in api/user if you want), which is still usable in the API route.
// pages/api/user
export async function getData() {
const response = await fetch(/* external API endpoint */)
const jsonData = await response.json()
return jsonData
}
export default async function handler(req, res) {
const jsonData = await getData()
res.status(200).json(jsonData)
}
But also allows you to re-use the getData function in getServerSideProps.
// pages/home
import { getData } from './api/user'
//...
export async function getServerSideProps(context) {
const jsonData = await getData()
//...
}
You want to use the logic that's in your API route directly in
getServerSideProps, rather than calling your internal API. That's
because getServerSideProps runs on the server just like the API routes
(making a request from the server to the server itself would be
pointless). You can read from the filesystem or access a database
directly from getServerSideProps
As I admit, what you say is correct but problem still exist. Assume you have your backend written and your api's are secured so fetching out logic from a secured and written backend seems to be annoying and wasting time and energy. Another disadvantage is that by fetching out logic from backend you must rewrite your own code to handle errors and authenticate user's and validate user request's that exist in your written backend. I wonder if it's possible to call api's within nextjs without fetching out logic from middlewars? The answer is positive here is my solution:
npm i node-mocks-http
import httpMocks from "node-mocks-http";
import newsController from "./api/news/newsController";
import logger from "../middlewares/logger";
import dbConnectMid from "../middlewares/dbconnect";
import NewsCard from "../components/newsCard";
export default function Home({ news }) {
return (
<section>
<h2>Latest News</h2>
<NewsCard news={news} />
</section>
);
}
export async function getServerSideProps() {
let req = httpMocks.createRequest();
let res = httpMocks.createResponse();
async function callMids(req, res, index, ...mids) {
index = index || 0;
if (index <= mids.length - 1)
await mids[index](req, res, () => callMids(req, res, ++index, ...mids));
}
await callMids(
req,
res,
null,
dbConnectMid,
logger,
newsController.sendAllNews
);
return {
props: { news: res._getJSONData() },
};
}
important NOTE: don't forget to use await next() instead of next() if you use my code in all of your middlewares or else you get an error.
Another solution: next connect has run method that do something like mycode but personally I had some problems with it; here is its link:
next connet run method to call next api's in serverSideProps
Just try to use useSWR, example below
import useSWR from 'swr'
import React from 'react';
//important to return only result, not Promise
const fetcher = (url) => fetch(url).then((res) => res.json());
const Categories = () => {
//getting data and error
const { data, error } = useSWR('/api/category/getCategories', fetcher)
if (error) return <div>Failed to load</div>
if (!data) return <div>Loading...</div>
if (data){
// {data} is completed, it's ok!
//your code here to make something with {data}
return (
<div>
//something here, example {data.name}
</div>
)
}
}
export default Categories
Please notice, fetch only supports absolute URLs, it's why I don't like to use it.
P.S. According to the docs, you can even use useSWR with SSR.
I'm trying to use the emulator only when it is available. connectAuthEmulator does not fail if the emulator is not available (i.e.: if I haven't ran firebase emulators:start). It fails later when I try to make a request.
To do this, I'm fetching the emulator URL (http://localhost:9099) using fetch. If the request succeeds, then I call connectAuthEmulator. Otherwise, I do nothing (uses the configured cloud service).
I have a problem where the fetch works, but connectAuthEmulator throws an error: auth/emulator-config-failed. For some weird reason, this seem to happen only when I'm logged in and I make no requests for about 15 seconds. If I spam requests however, the error never occurs.
import { initializeApp } from "firebase/app";
import { getAuth, connectAuthEmulator } from "firebase/auth";
const app = initializeApp({ /** ... */ });
const auth = getAuth(app);
if (NODE_ENV === "development") {
(async () => {
try {
const authEmulatorUrl = "http://localhost:9099";
await fetch(authEmulatorUrl);
connectAuthEmulator(auth, authEmulatorUrl, {
disableWarnings: true,
});
console.info("🎮 Firebase Auth: emulated");
} catch (e) {
console.info("🔥 Firebase Auth: not emulated");
}
})()
}
export { auth };
Any idea why this happens and how to fix it?
Solution #1
First, try http://127.0.0.1:9099 instead of http://localhost:9099 (don't forget to set this as your emulator host in firebase.json).
Solution #2
On top of using solution #1, try rendering your app after everything Firebase-related has init. You can do this by creating listeners on the status of your emulator connections.
src/config/firebase.ts
import { initializeApp } from "firebase/app";
import { getAuth, connectAuthEmulator } from "firebase/auth";
const app = initializeApp({ /** ... */ });
const auth = getAuth(app);
if (NODE_ENV === "development") {
// Create listener logic
window.emulatorsEvaluated = false;
window.emulatorsEvaluatedListeners = [];
window.onEmulatorsEvaluated = (listener: () => void) => {
if (window.emulatorsEvaluated) {
listener();
} else {
window.emulatorsEvaluatedListeners.push(listener);
}
};
(async () => {
try {
// Use 127.0.0.1 instead of localhost
const authEmulatorUrl = "http://127.0.0.1:9099";
await fetch(authEmulatorUrl);
connectAuthEmulator(auth, authEmulatorUrl, {
disableWarnings: true,
});
console.info("🎮 Firebase Auth: emulated");
} catch (e) {
console.info("🔥 Firebase Auth: not emulated");
}
// Indicate that the emulators have been evaluated
window.emulatorsEvaluated = true;
window.emulatorsEvaluatedListeners.forEach(
(listener: () => void) => {
listener();
}
);
})()
}
export { auth };
src/index.tsx
import React from "react";
import { createRoot } from "react-dom/client";
import "./config/firebase";
const root = createRoot(document.getElementById("root")!);
const Root = () => ({
/** Some JSX */
});
if (NODE_ENV === "development") {
window.onEmulatorsEvaluated(() => {
root.render(<Root />);
});
} else {
root.render(<Root />);
}
Solution #3
Forcibly change the value of auth._canInitEmulator ((auth as unknown as any)._canInitEmulator for TS) to true. This can have some unexpected side-effects (see answer), because some requests might go to your cloud before the emulator kicks in. This can be mitigated with solution #2 (which should prevent requests from firing)
(auth as unknown as any)._canInitEmulator = true;
connectAuthEmulator(auth, authEmulatorUrl, {
disableWarnings: true,
});
Full Answer
Part of the issue here is that I was performing connectAuthEmulator in an async function, when the documentation clearly states it should be called immediately (and synchronously) after getAuth. I was aware of this, but I did not see any other alternative to the issue of connecting to the emulator only when it is available.
I dug into the source code for connectAuthEmulator (permalink) and found out that the error is thrown here:
_assert(
authInternal._canInitEmulator,
authInternal,
AuthErrorCode.EMULATOR_CONFIG_FAILED
);
authInternal._canInitEmulator was false when the error occurred. There is only one place where this property is set to false, in _performFetchWithErrorHandling, here (permalink). This is because it's the first time it performs an API request for the Auth service. So I concluded that Firebase disallows the use of emulators after the first request using auth is made. This is probably to prevent making some requests on your cloud, then switching over to an emulator. So my call to connectAuthEmulator was probably done after a request was already made. I couldn't figure out where though. Even with my app not being loaded (solution #2), the error would still occur.
My conclusion was right, as I later found this error.ts file which pretty much says this:
[AuthErrorCode.EMULATOR_CONFIG_FAILED]:
'Auth instance has already been used to make a network call. Auth can ' +
'no longer be configured to use the emulator. Try calling ' +
'"connectAuthEmulator()" sooner.',
This whole investigation could've been done earlier if FirebaseError had shown this message instead of just auth/emulator-config-failed. It is visible if you console.log(error.message), but I was not aware of that at the time.
For some reason, using the localhost IP instead fixed it instantly and I never had the error again. I added the solution #2 and #3 as another measure after.
try http://127.0.0.1:9099 instead of http://localhost:9099 (don't forget to set this as your emulator host in firebase.json).
I think the root cause of this problem comes from React.StrictMode. This mode will re-render twice time. It might have been initiated twice time too. I am pretty sure because when I disabled the React.StrictMode and refreshed the browser many times. The error does not show up anymore.
I'm new to Next.js and I'm trying to understand the suggested structure and dealing with data between pages or components.
For instance, inside my page home.js, I fetch an internal API called /api/user.js which returns some user data from MongoDB. I am doing this by using fetch() to call the API route from within getServerSideProps(), which passes various props to the page after some calculations.
From my understanding, this is good for SEO, since props get fetched/modified server-side and the page gets them ready to render. But then I read in the Next.js documentation that you should not use fetch() to all an API route in getServerSideProps(). So what am I suppose to do to comply to good practice and good SEO?
The reason I'm not doing the required calculations for home.js in the API route itself is that I need more generic data from this API route, as I will use it in other pages as well.
I also have to consider caching, which client-side is very straightforward using SWR to fetch an internal API, but server-side I'm not yet sure how to achieve it.
home.js:
export default function Page({ prop1, prop2, prop3 }) {
// render etc.
}
export async function getServerSideProps(context) {
const session = await getSession(context)
let data = null
var aArray = [], bArray = [], cArray = []
const { db } = await connectToDatabase()
function shuffle(array) {
var currentIndex = array.length, temporaryValue, randomIndex;
while (0 !== currentIndex) {
randomIndex = Math.floor(Math.random() * currentIndex);
currentIndex -= 1;
temporaryValue = array[currentIndex];
array[currentIndex] = array[randomIndex];
array[randomIndex] = temporaryValue;
}
return array;
}
if (session) {
const hostname = process.env.NEXT_PUBLIC_SITE_URL
const options = { headers: { cookie: context.req.headers.cookie } }
const res = await fetch(`${hostname}/api/user`, options)
const json = await res.json()
if (json.data) { data = json.data }
// do some math with data ...
// connect to MongoDB and do some comparisons, etc.
But then I read in the Next.js documentation that you should not use fetch() to all an API route in getServerSideProps().
You want to use the logic that's in your API route directly in getServerSideProps, rather than calling your internal API. That's because getServerSideProps runs on the server just like the API routes (making a request from the server to the server itself would be pointless). You can read from the filesystem or access a database directly from getServerSideProps. Note that this only applies to calls to internal API routes - it's perfectly fine to call external APIs from getServerSideProps.
From Next.js getServerSideProps documentation:
It can be tempting to reach for an API Route when you want to fetch
data from the server, then call that API route from
getServerSideProps. This is an unnecessary and inefficient approach,
as it will cause an extra request to be made due to both
getServerSideProps and API Routes running on the server.
(...) Instead, directly import the logic used inside your API Route
into getServerSideProps. This could mean calling a CMS, database, or
other API directly from inside getServerSideProps.
(Note that the same applies when using getStaticProps/getStaticPaths methods)
Here's a small refactor example that allows you to have logic from an API route reused in getServerSideProps.
Let's assume you have this simple API route.
// pages/api/user
export default async function handler(req, res) {
// Using a fetch here but could be any async operation to an external source
const response = await fetch(/* external API endpoint */)
const jsonData = await response.json()
res.status(200).json(jsonData)
}
You can extract the fetching logic to a separate function (can still keep it in api/user if you want), which is still usable in the API route.
// pages/api/user
export async function getData() {
const response = await fetch(/* external API endpoint */)
const jsonData = await response.json()
return jsonData
}
export default async function handler(req, res) {
const jsonData = await getData()
res.status(200).json(jsonData)
}
But also allows you to re-use the getData function in getServerSideProps.
// pages/home
import { getData } from './api/user'
//...
export async function getServerSideProps(context) {
const jsonData = await getData()
//...
}
You want to use the logic that's in your API route directly in
getServerSideProps, rather than calling your internal API. That's
because getServerSideProps runs on the server just like the API routes
(making a request from the server to the server itself would be
pointless). You can read from the filesystem or access a database
directly from getServerSideProps
As I admit, what you say is correct but problem still exist. Assume you have your backend written and your api's are secured so fetching out logic from a secured and written backend seems to be annoying and wasting time and energy. Another disadvantage is that by fetching out logic from backend you must rewrite your own code to handle errors and authenticate user's and validate user request's that exist in your written backend. I wonder if it's possible to call api's within nextjs without fetching out logic from middlewars? The answer is positive here is my solution:
npm i node-mocks-http
import httpMocks from "node-mocks-http";
import newsController from "./api/news/newsController";
import logger from "../middlewares/logger";
import dbConnectMid from "../middlewares/dbconnect";
import NewsCard from "../components/newsCard";
export default function Home({ news }) {
return (
<section>
<h2>Latest News</h2>
<NewsCard news={news} />
</section>
);
}
export async function getServerSideProps() {
let req = httpMocks.createRequest();
let res = httpMocks.createResponse();
async function callMids(req, res, index, ...mids) {
index = index || 0;
if (index <= mids.length - 1)
await mids[index](req, res, () => callMids(req, res, ++index, ...mids));
}
await callMids(
req,
res,
null,
dbConnectMid,
logger,
newsController.sendAllNews
);
return {
props: { news: res._getJSONData() },
};
}
important NOTE: don't forget to use await next() instead of next() if you use my code in all of your middlewares or else you get an error.
Another solution: next connect has run method that do something like mycode but personally I had some problems with it; here is its link:
next connet run method to call next api's in serverSideProps
Just try to use useSWR, example below
import useSWR from 'swr'
import React from 'react';
//important to return only result, not Promise
const fetcher = (url) => fetch(url).then((res) => res.json());
const Categories = () => {
//getting data and error
const { data, error } = useSWR('/api/category/getCategories', fetcher)
if (error) return <div>Failed to load</div>
if (!data) return <div>Loading...</div>
if (data){
// {data} is completed, it's ok!
//your code here to make something with {data}
return (
<div>
//something here, example {data.name}
</div>
)
}
}
export default Categories
Please notice, fetch only supports absolute URLs, it's why I don't like to use it.
P.S. According to the docs, you can even use useSWR with SSR.
I'm trying to build an express server.
In the app.js file I create the server and try to connect to my mongodb.
import express from "express";
import dbo from "./db/conn.js";
const app = express();
app.listen(PORT, async () => {
await dbo.connectToDatabase();
log.info(`Server is running on port: ${PORT}`);
});
In file conn.js
const client = new MongoClient(uri);
let db: Db;
export async function connectToDatabase() {
try {
await client.connect();
db = client.db(dbName);
await db.command({ ping: 1 });
log.info(`Successfully connected to database: ${db.databaseName}.`);
} finally {
await client.close();
}
}
export async function getDb() {
return db;
}
export default { connectToDatabase, getDb };
And now in a seperate file, I wantt to use the database connection I created in the app.js file.
like this:
const collection = getDb().collection(collectionName);
// insert documents in collection.
but when I try to run the app I get error TypeError: Cannot read properties of undefined.
So, is there a way to wait until the db connection is established?
There are some problems with your code.
First, the getDb function returns promise. So you can't use the collection method right away. Remove the async keyword because the current getDb function is not an asynchronous function.
export function getDb() {
return db;
}
Second, in the try & finally statement, the finally block is executed unconditionally regardless of whether an exception occurs in the code. Therefore, after displaying the connection success log in the connectToDatabase, the connection closes immediately. In order to insert the data, the connection must be connected, so please erase the await client.close(); code in finally block from connectToDatabase.
Then you can add a document to the collection without a problem.
One way to accomplish what you're trying to do is to pass that connection to a class or function you define elsewhere.
Say this is your collection code (or whatever thing even outside a collection that needs a raw db connection instance in order to use):
class Collection {
constructor(db) {
this.db = db
//do whatever else needs to be setup
}
use(name) {
//or use it here
this.db.use(name)
}
}
export default Collection
Then in your main file some code:
import Collection from './Collection.js'
const collection = new Collection(getDb)
collection.use('some arbitrary name')
This is just one way to do it but there are other ways as well. Also if this is the only thing using your getDb() method above, you can remove it from your main and put it in here as a manager.
Hello guys my code is similar to this https://github.com/vercel/next.js/tree/canary/examples/with-redux-saga.
However, when I try to do this code:
export const getStaticProps = wrapper.getStaticProps(async ({ store }) => {
store.dispatch(initCategoriesRequest());
store.dispatch(END);
await store.sagaTask.toPromise();
});
It says:
Error: Actions must be plain objects. Use custom middleware for async actions.
and it points to store.dispatch(END).
Also, if i put async (ctx) instead of async ({ store }), I don't get any paramters (for example req, res, params, etc.) for ctx; there's only the store.
Maybe the problem is something with the configuration.
Late response, but you must import the END action from redux-saga.
e.g.
import { END } from 'redux-saga';