On a dynamically generated page, I want to redirect if the user is not logged in. I would do it with getServerSideProps like this:
export async function getServerSideProps({ req, res }) {
if (req.headers['x-user-id']) {
return {
props: {
//...
}
}
} else {
return {
redirect: {
destination: '/'
}
}
}
}
However, in my dynamically generated next.js page, I need to use getStaticPaths and getStaticProps together, and you can't use getStaticProps and getServerSideProps together. Therefore, for my page [username].js, I tried doing this:
export async function getStaticProps({ params, req, res }) {
const results = await Get('https://example.com/api/getUsers')
const i = results.findIndex(e => e.username === params.username)
if (req.headers['x-user-id']) {
return {
props: {
user: results[i]
}
}
} else {
return {
redirect: {
destination: '/'
}
}
}
}
This however gives me the error:
TypeError: Cannot read properties of undefined (reading 'headers')
How can I fix this?
Related
I have been trying to set user data into Sentry's scope globally, so every time there's an error or event, user info is passed to it.
My app is built in Next.js, so naturally I added the config as it is in Sentry's documentation for Next.js.
I haven't got the idea on where to add the Sentry.setUser({id: user.Id}) method in order for it to set the user globally.
So far I have added it to the Sentry's _error.js file, inside the getInitialProps method:
import NextErrorComponent from 'next/error';
import * as Sentry from '#sentry/nextjs';
import { getUser } from '../lib/session';
const MyError = ({ statusCode, hasGetInitialPropsRun, err }) => {
if (!hasGetInitialPropsRun && err) {
Sentry.captureException(err);
}
return <NextErrorComponent statusCode={statusCode} />;
};
MyError.getInitialProps = async (context) => {
const errorInitialProps = await NextErrorComponent.getInitialProps(context);
const { req, res, err, asPath } = context;
errorInitialProps.hasGetInitialPropsRun = true;
const user = await getUser(req, res);
// Set user information
if (user) {
console.log('Setting user');
Sentry.setUser({ id: user.Id });
}
else {
console.log('Removing user');
Sentry.configureScope(scope => scope.setUser(null));
}
if (res?.statusCode === 404) {
return errorInitialProps;
}
if (err) {
Sentry.captureException(err);
await Sentry.flush(2000);
return errorInitialProps;
}
Sentry.captureException(
new Error(`_error.js getInitialProps missing data at path: ${asPath}`),
);
await Sentry.flush(2000);
return errorInitialProps;
};
export default MyError;
But when trying to log errors, the user info doesn't show in Sentry, only the default user ip:
I have also tried setting the user after successful login, and still nothing..
Help is appreciated!!
Not sure if this is the right way, but the above solutions didn't work for me. So I tried calling setUser inside _app.tsx.
import { useEffect } from "react";
import { setUser } from "#sentry/nextjs";
import { UserProvider, useUser } from "#auth0/nextjs-auth0";
import type { AppProps } from "next/app";
function SentryUserManager() {
const { user } = useUser();
useEffect(() => {
if (user) {
setUser({
email: user.email ?? undefined,
username: user.name ?? undefined,
});
} else {
setUser(null);
}
}, [user]);
return null;
}
export default function MyApp({ Component, pageProps }: AppProps) {
return (
<UserProvider>
<Component {...pageProps} />
<SentryUserManager />
</UserProvider>
);
}
Still not sure why this worked for me and the other solutions didn't, but figured it was worth sharing.
I would suggest using the callback handler to set your Sentry user context.
import { handleAuth, handleLogin, handleCallback } from "#auth0/nextjs-auth0";
import * as Sentry from "#sentry/nextjs";
import { NextApiHandler } from "next";
const afterCallback = (_req, _res, session, _state) => {
Sentry.setUser({
id: session.user.sub,
email: session.user.email,
username: session.user.nickname,
name: session.user.name,
avatar: session.user.picture,
});
return session;
};
const handler: NextApiHandler = handleAuth({
async login(req, res) {
await handleLogin(req, res, {
returnTo: "/dashboard",
});
},
async callback(req, res) {
try {
await handleCallback(req, res, { afterCallback });
} catch (error) {
res.status(error.status || 500).end(error.message);
}
},
});
export default Sentry.withSentry(handler);
You can set the user in Sentry right after successful login
const handleLogin = {
try {
const res = await axios.post("/login", {"john#example.com", "password"})
if (res && res?.data) {
// Do other stuff
Sentry.setUser({ email: "john#example.com" });
}
}
}
Additionaly you can clear the user while logging out
const handleLogout = {
// Do othe stuff
Sentry.configureScope(scope => scope.setUser(null));
}
I followed the next docs regarding a custom error page. I want to reuse the error page if certain errors occur at getStaticProps. Something like:
const Page: NextPage<Props> = ({ pageData, error }: Props) => {
if (error) return <Error statusCode={500} />;
return <Page data={pageData} />;
};
export const getStaticProps: GetStaticProps = async ({
params,
}: {
params: { [prop: string]: string[] };
}) => {
const { slug } = params;
const {pageData, error} = getPageData(slug)
return {
props: {
pageData: page || null,
error: error || null,
},
};
};
export default Page;
The error page is just like in the docs:
function Error({ statusCode }) {
return (
<p>
{statusCode ? `An error ${statusCode} occurred on server` : 'An error occurred on client'}
</p>
);
}
Error.getInitialProps = ({ res, err }) => {
const statusCode = res ? res.statusCode : err ? err.statusCode : 404;
return { statusCode };
};
export default Error;
This works but the status code ist wrong. Nextjs still answers a request to this page with a status code 200. I need it to set the status code to 500 as if there was a server error.
you can change your page status code if you are using "getServerSideProps" or "getStaticProps", you only need to change the response status code before returning the page props:
const HomePage = ({pageProps}) => {
if (pageProps.hasError) {
return <CustomError status={error.status} message={error.message}/>
}
return <div>This is home page </div>
}
export const getServerSideProps = async ({req,res}) => {
try {
const homeData = await fetchHomeData()
return {
props: {
data: homeData
}
}
} catch (err) {
res.statusCode = exception.response.status /* this is the key part */
return {
props: {
hasError: true, error: {
status: exception.response.status || 500,
message: exception.message
}
}
}
}
}
In my route I have a parameter like: ?id=101
And I have some data like:
data () {
return {
record: {id: null}
}
}
Now, what I want to do this: if the query parameter is present, I want to update the id in the data with the id in the query variable. I tried doing this in the fetch, as follows:
async fetch ({ store, redirect, params, query }) {
this.record = {id: query.id}
}
However, nothing happens. I also tried calling a function from the fetch, but it says the method was not defined.
Can you please help?
Ok my answer got deleted. Find the answer below:
export default {
data () {
return {
record: {
id: null
}
}
},
created () {
this.fetchData()
},
watch: {
'$route': 'fetchData'
},
methods: {
fetchData () {
getRecord(this.$route.query.id, (err, id) => {
if (err) {
this.error = err.toString()
} else {
this.record.id = id
}
})
}
}
}
More info here:
https://router.vuejs.org/guide/advanced/data-fetching.html
aUtil.js
module.exports = {
successTrue: function(data) {
return { data: data, success: true };
},
isLoggedin: async (req, res) {
//decoded token in req header
//if decoded token success,
res.json(this.successTrue(req.decoded));
}
}
that function call in test.js
router.get('/check', aUtil.isLoggedin, async (req, res) => { ... })
I want to use the function above in that function.
But I keep getting errors.
ReferenceError: successTrue is not defined
I tried many ways.
insert 'const aUtil = require('./aUtil')`
change to 'res.json(successTrue( ... )'
Use this:
module.exports = {
successTrue: function() {
return { foo: 'bar' }
},
isLoggedin: function() {
console.log(this.successTrue())
}
}
You're exporting an object, so this refers to itself.
Also make sure you're binding aUtils if you're using it as middleware like so:
router.get('/check', aUtil.isLoggedin.bind(aUtil), async (req, res) => { ... })
Try this :
const aUtil = {
successTrue: function() { //return json obj },
isLoggedin: function() {
res.json(aUtil.successTrue( ... ));
}
}
module.exports = aUtil;
I'm trying to access a function that I'm later going to use for changing some database values. However whenever I try to build the project I get:
Error: Route.post() requires callback functions but got a [object Promise]
My initial function is:
import fetch from '../../../../core/fetch';
import history from '../../../../core/history';
export const BOXOFFICE_CHECKING_IN = 'BOXOFFICE_CHECKING_IN';
export const BOXOFFICE_CHECKED_IN = 'BOXOFFICE_CHECKED_IN';
export const BOXOFFICE_CHECKED_IN_ERROR = 'BOXOFFICE_CHECKED_IN_ERROR';
export default function checkIn() {
return async (dispatch, getState) => {
try {
dispatch({ type: BOXOFFICE_CHECKING_IN });
const state = getState();
const {
order: {
id: orderId,
},
} = state;
const response = await fetch(
`/api/event/orders/${orderId}/checkIn`, {
method: 'POST',
headers: {
Accept: 'application/json',
'Content-Type': 'application/json',
},
body: JSON.stringify({
order: orderId,
checkedIn: true,
}),
}
);
if (response.status === 200) {
dispatch({ type: BOOKING_CHECKED_IN });
} else {
const errorResponse = await response.json();
if (errorResponse.code === 'card_error') {
dispatch({ type: BOXOFFICE_CHECKED_IN_ERROR });
}
}
} catch (err) {
throw err;
}
};
}
Which then feeds to the api:
import checkIn from '../handlers/api/orders/checkInCustomer';
...
export default (resources) => {
const router = new Router();
...
router.post('/orders/:orderId/checkIn', checkIn(resources));
Which then reaches the final function I wish to use:
export default async function checkIn(req, res) {
console.log('this is working fully');
return true;
}
Any help is appreciated.
The problem is, you want to be passing the function checkIn, but when you call it using checkIn(resources) you're actually passing the return value (A promise that resolves to true).
You should be using:
router.post('/orders/:orderId/checkIn', checkIn);
Now, I'm assuming you want to do this because you want to pass resources into the router.post function, correct? What happens to the request and response objects?
Where does resources go?
v
export default async function checkIn(req, res) {
console.log('this is working fully');
return true;
}
You have a few ways of accomplishing what you're looking for.
Create a resources file, and import it. This is the ideal solution:
const db = mysql.connect(...);
const lang = lang.init();
console.log('This file is only called once!');
export default {
db,
lang,
};
And then in your code (/routes/checkIn.js):
import { db } from '../resources';
export default async function checkIn(req, res) {
//Access db here
//db.query...
}
Wrap your code in an intermediate function:
router.post('/orders/:orderId/checkIn', (req, res) => checkIn(req, res, resources));
Bind() resources to your checkIn function:
const db = mysql.connect(...);
const lang = lang.init();
const resources = {db, lang};
router.post('/orders/:orderId/checkIn', checkIn.bind(resources));
And then in your code (/routes/checkIn.js):
export default async function checkIn(req, res) {
//Access this.db here
//this.db.query...
}