Cannot set headers after they are sent to the client with res.writeHead() - next.js

I am trying to redirect a user to the login if he isn't authenticated. I hardcoded the jwt for now. This works, but I only get an error saying Error [ERR_HTTP_HEADERS_SENT]: Cannot set headers after they are sent to the client.
Since the function works I don't know what is wrong and couldn't really find an answer to it either. This is my code for reference:
function redirectUser(ctx, location) {
if (ctx.req) {
ctx.res.writeHead(302, { Location: location });
ctx.res.statusCode = 302;
ctx.res.setHeader(302, location);
ctx.res.end();
return { props: {} };
} else {
Router.push(location);
}
}
// getInitialProps disables automatic static optimization for pages that don't
// have getStaticProps. So article, category and home pages still get SSG.
// Hopefully we can replace this with getStaticProps once this issue is fixed:
// https://github.com/vercel/next.js/discussions/10949
MyApp.getInitialProps = async (ctx) => {
const jwt = false;
// Calls page's `getInitialProps` and fills `appProps.pageProps`
const appProps = await App.getInitialProps(ctx);
// Fetch global site settings from Strapi
const global = await fetchAPI("/global");
if (!jwt) {
if (ctx?.ctx.pathname === "/account") {
redirectUser(ctx.ctx, "/login");
}
}
// Pass the data to our page via props
return { ...appProps, pageProps: { global } };
};
Any help would be much appreciated.

The error "Error: Can't set headers after they are sent." means that you're already in the body, but some other function tried to set a header or statusCode. In your case it is the function ctx.res.setHeader(302, location); that's causing the issue.
After writeHead, the headers are baked in and you can only call res.write(body), and finally res.end(body).
You do not need to use setHeader when you are already using the writehead method.
Read here more about the writehead
So your redirectUser could be like :
function redirectUser(ctx, location) {
if (ctx.req) {
ctx.res.writeHead(302, { Location: location });
ctx.res.end();
return { props: {} };
} else {
Router.push(location);
}
}

Related

How to set api routes if you want your NEXTJS app to follow a subdomain?

I have set up a NEXTJS app that is under a subdomain and basically the structure is the following:
/Pages->
--/Sites
--/api
--/home
--/subdomain_logic
--_app.tsx
...config files...
As of this moment, if you go to domain.com you will be landing into another app that I developed so there is nothing configured outside of subdomain logic. If you go to subdomain.domain.com then you get all the logic ocurring into subdomain_logic. I want to set api routes but nextjs doesn't allow to set them outside of your api folder and if I leave them there those routes actually belong the domain.com app that I have in isolation. How would you create api routes on my situation?
Here is my middleware.ts file:
import { NextRequest, NextResponse } from "next/server";
export const config = {
matcher: [
"/",
"/([^/.]*)", // exclude `/public` files by matching all paths except for paths containing `.` (e.g. /logo.png)
"/site/:path*",
"/post/:path*",
"/_sites/:path*"
]
};
export default function middleware(req: NextRequest) {
const url = req.nextUrl;
const pathname = req.nextUrl.pathname.toString();
const hostname = req.headers.get("host");
if (!hostname)
return new Response(null, {
status: 400,
statusText: "No hostname found in request headers"
});
const currentHost =
process.env.VERCEL_ENV === `production` ||
process.env.VERCEL_ENV === `preview`
?
hostname
.replace(`.domain.com`, "")
.replace(`${process.env.VERCEL_URL}`, "")
.replace(`${process.env.NEXT_PUBLIC_VERCEL_URL}`, "")
: hostname.replace(`.localhost:3000`, "");
if (pathname.startsWith(`/_sites`))
return new Response(null, {
status: 404
});
if (
!pathname.includes(".")
) {
if (currentHost === "subdomain") {
if (
pathname === "/login" &&
(req.cookies["next-auth.session-token"] ||
req.cookies["__Secure-next-auth.session-token"])
) {
url.pathname = "/";
return NextResponse.redirect(url);
}
url.pathname = `/subdomain${url.pathname}`;
console.log(url);
return NextResponse.rewrite(url);
}
url.pathname = `${pathname}`;
return NextResponse.rewrite(url);
}
}
I would like to set up properly NextAuth if that give more clues into what could be the solution for my problem. Thanks for the help!
So where you are doing your redirect you basically want to do a check if its the api directory and not run those rewrites. Not sure what some of your code is doing but here is something that i am doing in a project with a similar setup.
If you reference your api endpoints via either the main or the subdomain it should still route correctly.
I am a bit confused by your question and how you are looking to integrate next auth, feel free to drop a comment if i've misunderstood anything.
import { NextRequest, NextResponse } from "next/server";
export default function middleware(req: NextRequest) {
const { pathname } = req.nextUrl;
const url = req.nextUrl.clone();
let hostname = req.headers.get("host");
hostname = hostname?.split(":")[0] ?? null;
const rootUrls = `${process.env.ROOT_URLS}`.split(",");
// only continue if its not a custom domain happening.
if (!rootUrls?.includes(hostname!)) {
// get the current subdomain or custom domain
const currentHost = hostname!.replace(`.${process.env.ROOT_URL}`, "");
if (pathname.endsWith("sitemap.xml")) {
url.pathname = `/_sites/${currentHost}/sitemap`;
return NextResponse.rewrite(url);
}
if (pathname.endsWith("robots.txt")) {
url.pathname = `/_sites/${currentHost}/robots`;
return NextResponse.rewrite(url);
}
// fail if trying to access the site directory
if (pathname.startsWith(`/_sites`)) {
return new Response(null, { status: 404 });
}
if (pathname.startsWith(`/_sites_previews`)) {
return new Response(null, { status: 404 });
}
if (
!pathname.includes(".") && // exclude all files in the public folder
!pathname.startsWith("/api") // exclude all API routes
) {
if (req.nextUrl.searchParams.get("preview")) {
url.pathname = `/_sites_previews/${currentHost}${pathname}`;
} else {
url.pathname = `/_sites/${currentHost}${pathname}`;
}
// rewrite to the current hostname under the pages/sites folder
// the main logic component will happen in pages/sites/[site]/index.tsx
return NextResponse.rewrite(url);
}
}
}

Looking for a right way to inject Vue data in axios interceptor

I have a Vue 3 plugin for popup messages. This plugin extract it methods with use function and looks like
export function usePopup () {
const { addModal, addMessage, addToast } = inject(PopupInjectionKey) as PopupMethods;
return { addModal, addMessage, addToast };
}
Now I'm trying to implement global axios interceptor and I want to use my modal message to show errors from requests.
So I created method:
export function registerInterceptorGeneric () {
axios.interceptors.response.use(function (response) {
return response;
}, function (error) {
if (error?.response) {
const { status, data, headers } = error.response;
const { addMessage } = usePopup();
switch (status) {
case 409:
addMessage(data.message, MessageType.Info);
break;
}
}
return Promise.reject(error);
});
}
I call this method from main.ts, but unfortunately, usePopup is obviously out of context. So my question is - how i can pass Context to this function? Or maybe I should call this function from App.ts with addMessage as argument? I'm new to Vue, so, what's the right way to do such things?

Read data from ngrx store directly

I saw in our codebase a lot of places using this pattern
getUrl() : string {
let url = ''
this.store.pipe(selectorConfig, take(1)).subscribe((config) => {
url = config.url
});
return url;
}
Just want to check if this is async? will the selectorConfig emit only when config changes so this is an async call? There are also someone saying ngrx store is a behaviourObject so the moment you subscribe, it emits last value so this effectively is a sync function.
Could anyone confirm which is correct?
Unless you make it asynchronous it's a synchronous operation. As is the Subject.next method.
// Synchronous
getUrl() : string {
let url = ''
console.log('START');
// considering that selectorConfig does not contain anything `asynchronous`, which it should not
this.store.pipe(selectorConfig, take(1)).subscribe((config) => {
url = config.url
console.log('URL:', config.url);
});
console.log('START');
return url;
}
/* OUTPUT
START
URL: <yourUrl>
END
*/
// Asynchronous
import { delay } from 'rxjs/operators';
getUrl() : string {
let url = ''
console.log('START');
// adding a delay operator makes it asynchronous
this.store.pipe(selectorConfig, delay(0), take(1)).subscribe((config) => {
url = config.url
console.log('URL:', config.url);
});
console.log('START');
return url;
}
/* OUTPUT
START
END
URL: <yourUrl>
*/
There's a nice explanation to your question here

How to redirect to starting point after authorizing with auth0 in a Nextjs application using #auth0/nextjs-auth0

I'm currently using auth0 to authenticate users in a Next.js application.
I'm using the #auth0/nextjs-auth0 SDK and following along with the documentation.
However, I'm having trouble figuring out how to redirect users dynamically after login based on the page they accessed the login form from.
In the app I’m currently trying to build, users can log in from “/” which is the home page, and from the navbar element in “/browse”. However, after logging in, it always redirects back to “/”, while I would like to redirect users to “/browse” or "/browse/[id] if that is where they began the login process from.
I’ve tried using https://community.auth0.com/t/redirecting-to-another-page-other-than-using-nextjs-auth0/66920 as a guide but this method only allows me to redirect to a pre-defined route. I would like to know how I could make the redirect URL dynamic.
Thanks in advance!
Edit: I’ve managed to find a solution for now by digging in to the req object and setting the returnTo value to “referer”.
import { handleAuth, handleLogin } from '#auth0/nextjs-auth0';
const getLoginState = (req, loginOptions) => {
return {
returnTo: req.headers.referer
};
};
export default handleAuth({
async login(req, res) {
try {
await handleLogin(req, res, { getLoginState });
} catch (err) {
res.status(err.status ?? 500).end(err.message)
}
}
});
I’m not seeing any obvious problems so far but I’m not entirely sure if this method has any drawbacks, so I would appreciate any feedback.
How about this?
Step 1: Initialize Auth0 SDK
https://auth0.github.io/nextjs-auth0/modules/instance.html#initauth0
# /lib/auth0,js
import { initAuth0 } from "#auth0/nextjs-auth0";
export default initAuth0({
secret: process.env.SESSION_COOKIE_SECRET,
issuerBaseURL: process.env.NEXT_PUBLIC_AUTH0_DOMAIN,
baseURL: process.env.NEXT_PUBLIC_BASE_URL,
clientID: process.env.NEXT_PUBLIC_AUTH0_CLIENT_ID,
clientSecret: process.env.AUTH0_CLIENT_SECRET,
routes: {
callback:
process.env.NEXT_PUBLIC_REDIRECT_URI ||
"http://localhost:3000/api/auth/callback",
postLogoutRedirect:
process.env.NEXT_PUBLIC_POST_LOGOUT_REDIRECT_URI ||
"http://localhost:3000",
},
authorizationParams: {
response_type: "code",
scope: process.env.NEXT_PUBLIC_AUTH0_SCOPE,
},
session: {
absoluteDuration: process.env.SESSION_COOKIE_LIFETIME,
},
});
Step 2: Configure Login
https://auth0.github.io/nextjs-auth0/modules/handlers_login.html#handlelogin
https://auth0.github.io/nextjs-auth0/interfaces/handlers_login.loginoptions.html#returnto
# /pages/api/auth/login.js
import auth0 from "../../../lib/auth0";
export default async function login(req, res) {
let options = {
returnTo: 'http://localhost:3000/dashboard'
}
try {
await auth0.handleLogin(req, res, options);
} catch (error) {
console.error(error);
res.status(error.status || 500).end(error.message);
}
}
Now you will land on the dashboard page after successfully authenticating.
Step 3: Helpful Sanity Check
create /pages/api/auth/callback.js with the following content
import auth0 from "../../../lib/auth0";
const afterCallback = (req, res, session, state) => {
// console.log(session)
console.log(state)
return session
};
export default async function callback(req, res) {
try {
console.log(auth0)
await auth0.handleCallback(req, res, { afterCallback });
} catch (error) {
console.error(error);
res.status(error.status || 500).end(error.message);
}
}
Try logging in and look for the state in the console,
{ returnTo: 'http://localhost:3000/dashboard' }
Cheers!

How to cutomise the url sent with Meteor Accounts.ui forgetpassword

I'm using accounts.ui for forget password
Accounts.forgotPassword({email: "test#test.com"}, function (e, r) {
if (e) {
console.log(e.reason);
} else {
// success
}
});
When I send the email for forget password I simply get below email
Can someone tell me how to simply change the url and send the token with the same token
I tried using below code but that didn't work
main.js(client)
Meteor.startup(() => {
Accounts.resetPassword.text = function(user, url) {
url = url.replace('#/', '/');
console.log("url ==== ", url)
return `Click this link to reset your password: ${url}`;
}
});
On the server side:
Accounts.emailTemplates.resetPassword.subject = function () {
return "Forgot your password?";
};
Accounts.emailTemplates.resetPassword.text = function (user, url) {
return "Click this link to reset your password:\n\n" + url;
};
Read: https://docs.meteor.com/api/passwords.html#Accounts-emailTemplates
To change the resetPassword url to a custom one you have to run below code on your server (inside of /server/main.js file).
Accounts.urls.resetPassword = function (token) {
return FlowRouter.url("/reset-password/:token/", { token });
};
In this case, I am using a FlowRouter to generate that URL but you could technically just return a template literal if you like.
If the above code is in the server main.js file and you run Accounts.forgotPassword() function on the frontend from a localhost, it would generate this link:
http://localhost:3000/reset-password/C9cGfgaLEgGXbCVYJcCLnDYiRi3XJpmt2irLOOaKe56

Resources