I'm trying to add the the X-Robots-Tag header to all Next.js HTTP responses based on something in the environment the server is deployed to -- whether that is an environment variable (my preference) or anything else.
My Next.js application is deployed to two environments: an integration testing environment that uses the production Next.js build (NODE_ENV="production") but is connected to non-prod services, and the actual production environment that serves user traffic. I want to add the header only to the integration testing environment.
I've tried adding the header conditionally based on process.env.INTEGRATIONTESTENV in headers() in next.config.js, but any env var like process.env.XYZ seems to be evaluated at build time, not at runtime. For example, this doesn't work, even though the INTEGRATION_TEST_ENV environment variable is set to the string "true" on the server:
headers() {
if (process.env.INTEGRATION_TEST_ENV === "true") {
console.log("This code will never be run. The condition never evaluates to true, despite the runtime env var actually being set to 'true'.")
return [
{
source: "/:path*",
headers: [
{
key: "X-Robots-Tag",
value: "none",
},
],
},
]
}
},
I can't use next.config.js's phases either, since both my integration test and "real production" are running the production build and production server.
A custom server might solve the problem, but it seems like overkill, especially with the loss of automatic static optimization.
Is there any way to add a header based on a runtime environment variable?
A non-getServerSideProps(context) option would be to use a _middleware page:
// pages/_middleware.js
import { NextResponse } from "next/server";
export function middleware() {
const res = NextResponse.next();
// `process.env` evaluated at build time
if (process.env.INTEGRATION_TEST_ENV === "true") {
res.headers.set("X-Robots-Tag", "none");
}
return res;
}
I'm not sure if you're just compiling once then each deployment target gets the same bundle (and so the environment variable would be "baked-in"), but if you can find a workaround for that, this could potentially work.
And the other approach mentioned in the comments earlier:
export default function Home() {
return "Hello, world!";
}
// automatic static optimization no longer applies
export function getServerSideProps(context) {
if (process.env.INTEGRATION_TEST_ENV === "true") {
context.res.setHeader("X-Robots-Tag", "none");
}
return {
props: {},
};
}
Related
As the question states, how can I disable the bodyparser for one API route method, but enable it for another?
Here's part of my API route:
const handler = async (req, res) => {
if (req.method === "POST") {
...
} else if (req.method === "DELETE") {
...
} else {
res.status(405).json({
data: null,
error: "Method not allowed."
});
return;
};
};
export const config = {
api: {
bodyParser: false,
},
};
export default handler;
You'll see at the bottom of the code block that the body parser applies to both methods at the moment. I need to use the body parser for the DELETE method so that I can access a variable set within the body, but it needs to be disabled for the POST method (handling of multipart forms for image uploads).
I've thought about splitting these into two different files as follows:
Current setup:
/api/advert/[advertId]/image/index.jsx
Potential setup:
/api/advert/[advertId]/image/post.jsx
/api/advert/[advertId]/image/delete.jsx
While this would work, it would deviate the structure from the rest of my code and so I was wondering if there is a cleaner way to achieve this? (Preferably without the use of additional middleware)
Thanks
I'm trying to upload my nuxt app to Firebase as cloud function. The problem is that in the nuxtServerInit action I'm trying to call a plugin function, which apparently is not yet defined at that moment, because an error is thrown: (ERROR: this.$myPlugin is not a function). The code works in dev mode, it's just after upload to Firebase it fails.
The setup is as follows:
myPlugin.js
let env, auth, app, $store;
export default (context, inject) => {
env = context.app.context.env;
auth = context.app.$fire.auth;
app = context.app;
$store = context.store;
inject('myPlugin', myPlugin);
};
async function myPlugin(...) {... }
nuxt.config.js
plugins: [
{ src: '~/plugins/myPlugin', mode: 'all' }, // with no mode specified it fails too
],
vuex index.js
export const actions = {
async nuxtServerInit({ dispatch, commit }, { req }) {
const tl = await dispatch("initAction");
return tl;
}
}
vuex someModule.js
const actions = {
initAction({ commit }) {
return this.$myPlugin(...).then(...) // this line throws '$myPlugin is not a function' error
}
}
What can be the reason for the different behaviour in dev and in prod modes and how could I fix the problem?
UPDATE:
After further testing I established that the problem is not caused by the nuxtServerInit timing. I moved the call of the initAction from nuxtServerInit to a page's created hook. However the same error appears: this.$query is not a function.
The problem occured, because js files were not getting fully loaded due to CORB errors caused by incorrect configuration. Details described in this question.
I installed and add this code to my nuxt.config.js and it works perfectly fine. (Link to package)
modules: [
['#nuxtjs/google-tag-manager', { id: 'GTM-XXXXXXX' }],
]
Now I am trying to implement instead of a static ID a function which will return an ID.
I tried to add this lines into my nuxt.config. js but it is not working. Obviously I have to put it somewhere else or so...
This is what I tried
nuxt.config.js
const code = '1234567'
id: () => {
return 'GTM-' + code
}
export default {
...
modules: [
['#nuxtjs/google-tag-manager', { id: id }],
]
...
}
What would be the correct way implementing this?
I would like to do something like that at the end.
modules: [
['#nuxtjs/google-tag-manager', {
id: ({ req }) => {
if (req.headers.referer == "exmple.com")
return 'GTM-156'
if (req.headers.referer == "exmple.it")
return 'GTM-24424'
if (req.headers.referer == "exmple.es")
return 'GTM-2424'
}
}]]
EDIT:
I solved my problem by rewriting the whole module. It is not possible to use this Module because it is loaded only on build time. I rewrote the module and moved the code into nuxtServerInit.
nuxtServerInit is called on each request (modules only onetime). In the request I asked from which domain the request is coming. Depending on the domain I add different google-tag-manager id's to the head and the plugin.
From package docs:
modules: [
['#nuxtjs/google-tag-manager', {
id: () => {
return axios.get('http://example.com/')
.then(({ data }) => {
return data.gtm_id
})
}
}]]
You can use process.env.NODE_ENV inside function which will return an ID
Edit 1
To put the gtm id, depending on req.headers.referer you need to provide context to the function returning the id. This can be done in middleware
See how it works here
https://github.com/nuxt-community/modules/blob/master/packages/google-tag-manager/plugin.js
Edit 2
As far as I understand your question, it will not work to have a query context in the config.
Look at i18n middleware: request.locale - > store - > update modules (router, vuetify, moment, etc.)
https://nuxtjs.org/examples/i18n/
~/middleware/gtm.js
export default function ({ app, store }) {
// app.$gtm contains id, you can set another from store
}
don't forget to add middleware to the page
page.vue
export default {
middleware: ['gtm']
}
I changed the location of my cloud functions from "us-central1" to "europe-west1" but I can't change the location of my functions on the client side which is a compulsory step for it to work according to the documentation.
(IDE tells me that no argument is expected on 'functions' when i do:
firebase.initializeApp(config).functions("europe-west1");
As an attempt to solve my problem I updated the three dependancies below with no result.
firebase-tools#latest
firebase-functions#latest
firebase-admin#latest
The problem is still here.
You should visit the following documentation page.
https://firebase.google.com/docs/web/setup
https://firebase.google.com/docs/functions/manage-functions#modify-region
https://firebase.google.com/docs/functions/locations
client side
Use firebase.app.App.
https://firebase.google.com/docs/reference/js/firebase.app.App#functions
Not admin.app.App. The firebase-admin only use on the server side.
https://firebase.google.com/docs/reference/admin/node/admin.app.App
Set the specified regions for a client app.
var firebase = require("firebase/app");
require("firebase/functions");
var config = {
// ...
};
firebase.initializeApp(config).;
var functions = firebase.app().functions('europe-west1');
server side(Cloud Functions)
Set the specified regions for each function.
const functions = require('firebase-functions');
exports.webhookEurope = functions
.region('europe-west1')
.https.onRequest((req, res) => {
res.send("Hello");
});
If you are changing the specified regions for a function that's handling production traffic, you can prevent event loss by performing these steps in order:
Rename the function, and change its region or regions as desired.
Deploy the renamed function, which results in temporarily running the same code in both sets of regions.
Delete the previous function.
I finally managed to fix my situation, by reinstalling ionic.
Plus .functions("europe-west1") has to be put on every call, not only in app.module.ts
With Typescript, you set the region like this:
export const myFunction = functions.region("europe-west2").firestore.document("users/{userId}")
Got it working this way!
const firebaseApp = firebase.initializeApp(firebaseConfig);
async signup(provider, info) {
if (!this.isValid) {
return;
}
try {
switch (provider) {
case this.EMAIL:
return await firebaseAuth().createUserWithEmailAndPassword(
info.email,
info.password
).then(authUser => {
const addUser = firebaseApp.functions('europe-west1').httpsCallable('addUser');
addUser({email: info.email,
name: info.name})
return authUser
})
default:
}
} catch (error) {
return error;
}
}
How can I know in babel plugin whether files currently transpiled by babel are transpiled for server or client/browser package?
Meteor recently implemented option caller available in babel 7. To use it and access information in plugin, one can access Babel.caller poperty like this:
let caller;
module.exports = function(Babel) {
Babel.caller(function(c) {
caller = { ...c };
});
return {
visitor: {
BlockStatement(){
console.log(caller); // logs e.g. {name: "meteor", arch: "web.browser.legacy"}
}
}
};
};