Import NPM package only for server side rendering(SSR) in Next.js App - reactjs

I am trying to use one private NPM module only when the page is loading for the first time and rendering is happening on the server. I am using the following piece of code but I am still able to see my package in chunks in client.html using the build analyzer.
if (typeof window === 'undefined') {
const content = await import('#abc/my-npm-package');
return conent(payload);
} else {
return null;
}

Excellent question, I've poked around the Next.js's code and found out that in order to code that will be exists only on the server you need to use some variable which is passed to webpack.DefinePlugin (which at build time will replace the usage with the value, this what allows the tree-shake to work).
From this line, there is a special variable process.browser which will be true only in the browser.
Change your code to
if (!process.browser) {
const content = await import('#abc/my-npm-package');
return conent(payload);
} else {
return null;
}
Update 16.03.2022
Next.js will deprecate process.browser soon, the new recommended way to distinguish between client & server is by checking type of window === 'undefined' // means that it's server side.
if (typeof window === 'undefined') { // server side
const content = await import('#abc/my-npm-package');
return content(payload);
} else { // client side
return null;
}

Related

Authentication between two websites?

I am working on superset integration with kepler. so far, I have cloned the superset code and configured in local and for kepler I have created a react application with kepler plugin.
Now in Superset I have included a button named 'View in Kepler' to allow the user to navigate to my kepler application.
In Superset there is a login screen to validate the user but while redirecting how to perform the same?
Code that performing redirecting logic with json data(dashboard data) is attached below
const res = userListJson; // Proceed and get data from dataFromListOfUsersState function
if (typeof window !== 'undefined') {
if(check_time == true){
window.location.href = "http://localhost:3000/header?dashboard=time&filename="+res.data+"";
}else {
window.location.href = "http://localhost:3000/header?dashboard=header&filename="+res.data+"";
}
}
}).catch((error) => {
if (error.response_2) {
console.log(error.response_2)
console.log(error.response_2.status)
console.log(error.response_2.headers)
}
});

How do I break the infinite redirect loop I've created in Next JS?

This application is being hosted on S3, entirely static (using next export), and we're routing all 404 errors to index.html in order to let those be handled on the client side so we can take advantage of dynamic routing. To handle this, I have the following in my _app.tsx file:
const { asPath, pathname, ...router } = useRouter();
// check if redirect
React.useEffect(() => {
if (pathname === '/' && asPath !== pathname) {
router.replace(asPath, undefined, { shallow: true });
}
}, [asPath]);
This works, for the dynamic routing aspect, but it introduces a new bug: when I navigate to a page that actually doesn't exist, like /fffff, there's an infinite loop of the app trying to reroute to /fffff. Ideally, it would only try to reroute once, and then default to the 404.tsx or _error.tsx page. I've tried creating a stateful boolean like hasRedirected and then just marking that as true in the useEffect, but that didn't work because the page is actually refreshing and thus resetting state on each router.replace call. How do I handle this error and break out of the loop?
update: The issue seems to be that when I call router.replace, Next doesn't find a path to match /fffff, so its default behavior is to try asking the server for the route by refreshing. I need to disable or intercept this behavior somehow.
The solution we ended up finding, which works quite well, uses session storage to store a hasRedirected variable that gets deleted after being read. Here's the code:
React.useEffect(() => {
if (router.isReady) {
const isRedirect = pathname === '/' && asPath !== pathname;
if (sessionStorage.getItem('hasRedirected')) {
sessionStorage.removeItem('hasRedirected');
if (isRedirect) router.replace('/404');
} else if (isRedirect) {
sessionStorage.setItem('hasRedirected', 'true');
router.replace(asPath);
}
}
}, [asPath, pathname, router.isReady]);

Property `deleteVisual` doesn't exist on type Page

I have a powerbi report embedded using Angular. I want to delete Visuals of the report. Here is the code I implemented for deleteVisual function.
deleteVisual() {
// Get report
const report = await this.reportObj.getReport();
if (!report){
console.log(“Report Not available”);
return;
}
// Get all the pages of the report
const pages = await report.getPages();
// Check if all the pages of the report deleted
if (pages.length === 0) {
console.log(“No pages found”);
return;
}
// Get active page of the report
const activePage = pages.find((page) => page.isActive);
if (activePage)
// Get all visuals in the active page of the report
const visuals = await activePage.getVisuals();
if (visuals.length === 0) {
console.log('No visuals found.');
return;
}
// Get first visible visual
const visual = visuals.find((v) => v.layout.displayState?.mode ===
models.VisualContainerDisplayMode.Visible);
if (!visual) {
console.log('No visible visual available to delete.');
return;
}
try {
// Delete the visual using powerbi-report-authoring
const response = await activePage.deleteVisual(visual.name);
console.log(`${visual.type} , visual was deleted.`);
return response;
} catch (error) {
console.error(error);
}
}
I am getting error saying Property deleteVisual doesn't exist on type Page. Also why getVisuals, getReport , even deletePage working fine but getting error while using this deleteVisual. I want to attach the ss of error but i dont have enough reputation to post images.Can anyone help me to solve this problem.
To use deleteVisual please install powerbi-report-authoring library.
Using npm you can install by this command
npm i powerbi-report-authoring
References:
https://learn.microsoft.com/javascript/api/overview/powerbi/remove-visual
https://www.npmjs.com/package/powerbi-report-authoring

How do you conditionally add condtions to a Firestore query in with the JavaScript SDK or React Native Firebase?

For the sake of handeling infinite scroll, I created a function that add conditions to the query according to the desired behavior:
const queryDocs = async (behavior: string = 'start', limit: number = 25) => {
let augmentedQuery: Query<DocumentData> | CollectionReference<DocumentData> = query
try {
if (behavior === 'start' || behavior === 'limited') {
augmentedQuery = augmentedQuery.limit(limit)
}
if (behavior === 'next' && lastItem) {
augmentedQuery = augmentedQuery.limit(limit).startAfter(lastItem)
}
const snapshot = await augmentedQuery.get()
if (behavior === 'start' || behavior === 'next') {
setLastItem(snapshot.docs[snapshot.docs.length - 1])
}
return snapshot
} catch (error) {
throw functionError(error, 'queryDocs', [behavior, limit], 'useCollection')
}
}
I get the following error:
on queryData("start",1) in module useCollection: Attempted to assign to readonly property.
Can we take take a previous query and add a .where or .limit?
Is there a solution to handle conditional queries?
Thanks
Edit: As Doug stated in the discussion, it is perfectly possible to chain queries like this. The error comes from another source. This code was into a shared package that was recompiled from TypeScript to ES6 and then used in React Native. After merging all the code into the React Native directory the error changed. I had a request to create a composite index with the link to do so, what is quite straightforward.

How can I utilise ApplicationInsights-JS in a service worker?

I am currently using ApplicationInsights-JS in my progressive web app. It works in my react components as I can import what I need from the relevant npm packages.
In my service worker however, I can only import logic using importScripts.
I did manage to find a CDN for ApplicationInsights-JS on their Github page however it seems that in order to initialise app insights using this library you need to have access to window in order to store the appinsights, which you cannot do from a service worker.
I tried to use the web snippet approach since the CDN seemed to be
related to that particular library, but I can't use window and am not sure how else to implement this solution.
This is a copy paste of the suggested snippet to init the app insights object from: https://github.com/Microsoft/ApplicationInsights-JS
importScripts('https://az416426.vo.msecnd.net/beta/ai.2.min.js');
const sdkInstance = 'appInsightsSDK';
window[sdkInstance] = 'appInsights';
const aiName = window[sdkInstance];
const aisdk =
window[aiName] ||
(function(e) {
function n(e) {
i[e] = function() {
const n = arguments;
i.queue.push(function() {
i[e](...n);
});
};
}
let i = { config: e };
i.initialize = !0;
const a = document;
const t = window;
setTimeout(function() {
const n = a.createElement('script');
(n.src = e.url || 'https://az416426.vo.msecnd.net/next/ai.2.min.js'),
a.getElementsByTagName('script')[0].parentNode.appendChild(n);
});
try {
i.cookie = a.cookie;
} catch (e) {}
(i.queue = []), (i.version = 2);
for (
const r = [
'Event',
'PageView',
'Exception',
'Trace',
'DependencyData',
'Metric',
'PageViewPerformance'
];
r.length;
)
n(`track${r.pop()}`);
n('startTrackPage'), n('stopTrackPage');
const o = `Track${r[0]}`;
if (
(n(`start${o}`),
n(`stop${o}`),
!(
!0 === e.disableExceptionTracking ||
(e.extensionConfig &&
e.extensionConfig.ApplicationInsightsAnalytics &&
!0 ===
e.extensionConfig.ApplicationInsightsAnalytics
.disableExceptionTracking)
))
) {
n(`_${(r = 'onerror')}`);
const s = t[r];
(t[r] = function(e, n, a, t, o) {
const c = s && s(e, n, a, t, o);
return (
!0 !== c &&
i[`_${r}`]({
message: e,
url: n,
lineNumber: a,
columnNumber: t,
error: o
}),
c
);
}),
(e.autoExceptionInstrumented = !0);
}
return i;
})({ instrumentationKey: 'xxx-xxx-xxx-xxx-xxx' });
(window[aiName] = aisdk),
aisdk.queue && aisdk.queue.length === 0 && aisdk.trackPageView({});
I get window is not defined which is expected, but I'm not sure how else I can make use of this library from the service worker.
Has anyone else had a similar implementation in which they successfully logged telemetry using ApplicationInsights from a service worker?
I realised that I was over complicating this.
Since I only needed to track a custom event, and didn't need all the automated page tracking etc that appInsights does, I ended up doing a fetch from my service worker.
I just copied the header and body format from the requests that I made using my react pages.
The below successfully logged telemetry to my app insights dashboard:
fetch(url, {
method: 'post',
headers: {
'Content-type': 'application/json'
},
body: JSON.stringify([
{
time: '2019-05-02T15:56:37.589Z',
iKey: 'INSTRUMENTATION_KEY',
name:
'Microsoft.ApplicationInsights.INSTRUMENTATION_KEY.Event',
tags: {
'ai.user.id': 'l6Tey',
'ai.session.id': 'TL+Ry',
'ai.device.id': 'browser',
'ai.device.type': 'Browser',
'ai.operation.id': 'HUfNE',
SampleRate: '100',
// eslint-disable-next-line no-script-url
'ai.internal.sdkVersion': 'javascript:2.0.0-rc4'
},
data: {
baseType: 'EventData',
baseData: {
ver: 2,
name: 'Testing manual event',
properties: {},
measurements: {}
}
}
}
])
})
.then(json)
.then(function(data) {
})
.catch(function(error) {
});
I've almost managed to use Microsoft Application Insights in our app's service worker.
The key parts are:
Using the lightweight version of appInsights (see this small remark at 4th step) with importScripts('https://az416426.vo.msecnd.net/next/aib.2.min.js').
Initialize an appInsights object:
appInsights = new Microsoft.AppInsights.AppInsights({ instrumentationKey: "[replace with your own key]" });
when track needed (during onpush event or onnotificationclick), go for appInsight.track({ eventItemFields }) then appInsights.flush().
I've said "almost" because the flush part seems to not working, I've got: "Sender was not initialized" internal error after enabling debugging.
I will publish here a working sample code if I successfully manage this issue.
References:
https://github.com/Azure-Samples/applicationinsights-web-sample1/blob/master/testlightsku.html
This response to the question: How to add analytics for Push notifications.
Using the Web SDK in a service worker is troublesome. The full version depends on a window object, while the basic SDK depends on Beacon or XmlHttpRequest for sending the messages (in file https://github.com/microsoft/ApplicationInsights-JS/blob/master/channels/applicationinsights-channel-js/src/Sender.ts):
if (!_self._senderConfig.isBeaconApiDisabled() && Util.IsBeaconApiSupported()) {
_self._sender = _beaconSender;
} else {
if (typeof XMLHttpRequest !== undefined) {
const xhr:any = getGlobalInst("XMLHttpRequest");
if(xhr) {
const testXhr = new xhr();
if ("withCredentials" in testXhr) {
_self._sender = _xhrSender;
_self._XMLHttpRequestSupported = true;
} else if (typeof XDomainRequest !== undefined) {
_self._sender = _xdrSender; // IE 8 and 9
}
}
}
}
At the moment Application Insights SDK does not seem to support service workers. Rajars solution seems to be the best option for now.
Update: There is an issue in the Github Repo about this: https://github.com/microsoft/ApplicationInsights-JS/issues/1436
A suggestion that works is by using the basic/lightweight version of Application Insights (as mentioned by Rajar) and adding a XMLHttpRequest polyfill (that uses the fetch api) before inititializing Application Insights. After that you can use the lightweight version.
An example can be found here: https://github.com/Pkiri/pwa-ai
I was trying to use AppInsightsSDK in E2E tests environment (pupeteer) and when I tried to log event or metric I got with "Sender was not initialized" error.
As #Pkiri mentioned one would need XMLHttpRequest polyfill to solve the issue. Although my scenario is not directly related to Service worker I wanted to mention that #Pkiri answer is not entirely true, because one can also use globalThis, self, window or global to get the same result according to SDK source code function getGlobalInst("XMLHttpRequest"); resolves to
function getGlobal() {
if (typeof globalThis !== strShimUndefined && globalThis) {
return globalThis;
}
if (typeof self !== strShimUndefined && self) {
return self;
}
if (typeof window !== strShimUndefined && window) {
return window;
}
if (typeof global !== strShimUndefined && global) {
return global;
}
return null;
}
And for my scenario this was a valid solution
const appInsights = new ApplicationInsights({
config: {
instrumentationKey: 'AppInsights_InstrumentationKey',
},
});
global.XMLHttpRequest = require("xmlhttprequest").XMLHttpRequest;
global.appInsights = appInsights.loadAppInsights();

Resources