I am using Next.js 14 along with Highcharts to render multiple charts on the same page.
Depending on the page, I may have between 3 to 5 charts displayed simultaneously. Although the data sets for the charts are not very complex, I am experiencing significant performance warnings, such as:
[Violation] 'message' handler took xxms
These warnings mainly occur when I switch between pages or when multiple charts are rendered at the same time.
Project Setup
- Next.js 14 with App Router.
- I use dynamic imports with ssr: false to lazily load the charts client-side.
- Each type of chart (like line charts, bar charts, etc.) uses a common BaseChart component, where I pass the chart configurations.
Here is an overview of my setup:
BaseGraph.tsx
import React, { useEffect, useRef } from 'react';
import Highcharts, { type SeriesOptionsType } from 'highcharts';
import HighchartsReact from 'highcharts-react-official';
import AccessibilityModule from 'highcharts/modules/accessibility';
import useMessages from '@/services/hooks/useMessages';
import NoDataToDisplay from 'highcharts/modules/no-data-to-display';
import { ChartBreakPoints, ChartTypes } from '@/services/charts/ChartService';
import { Colors } from '@/utils/colors';
import HighchartsMap from 'highcharts/modules/map';
import { merge } from '@/utils/Highcharts';
interface BaseChartProps {
chartType: string
chartData: any
chartOptions: Highcharts.Options
isLoading: boolean
setIsLoading: (isLoading: boolean) => void
yAxisUnit?: string
chartId: string
title?: string
isForHomePage?: boolean
}
export default function BaseChart ({
chartType,
chartData,
isLoading,
setIsLoading,
chartOptions,
yAxisUnit,
chartId,
title,
isForHomePage = false
}: Readonly<BaseChartProps>): React.JSX.Element {
const messages = useMessages();
const chartRef = useRef<HighchartsReact.RefObject>(null);
const chart = chartRef.current?.chart as any;
useEffect(() => {
NoDataToDisplay(Highcharts);
AccessibilityModule(Highcharts);
HighchartsMap(Highcharts);
Highcharts.setOptions({
lang: {
noData: messages('charts.no-data-message'),
decimalPoint: ','
}
});
}, []);
const commonOptions: Highcharts.Options = {
chart: {
type: chartType,
animation: false,
},
noData: {
style: {
fontWeight: '600',
fontSize: '16px',
color: '#0D0D0D'
}
},
tooltip: {
xDateFormat: '%d/%m/%Y',
borderRadius: 8,
shadow: {
color: '#000000',
opacity: 0.02,
offsetX: 0,
offsetY: 2,
width: 10
},
shared: false,
useHTML: true,
},
legend: {
itemHoverStyle: {
color: '#0D0D0D'
},
alignColumns: false,
},
plotOptions: {
series: {
events: {
legendItemClick: function (e) {
e.preventDefault();
}
},
animation: false
}
},
series: chartData as SeriesOptionsType[];
};
const finalOptions: Highcharts.Options = merge(commonOptions, chartOptions);
return (
<section id={chartId}>
<HighchartsReact
highcharts={Highcharts}
options={finalOptions}
ref={chartRef}
constructorType={chartType === ChartTypes.MAP ? 'mapChart' : undefined}
/>
</section>
);
}
The BaseGraph is then used inside every type of graph, for exemple a line chart :
LineChart.tsx
'use client';
import React from 'react';
import type Highcharts from 'highcharts';
import ReactDOMServer from 'react-dom/server';
import LineChartTooltip from './LineChartTooltip';
import { type LineChartEvolDataType } from '@/services/charts/types/ChartDataTypes';
import { ChartTypes } from '@/services/charts/ChartService';
import LazyBaseChart from '@/components/charts/common/LazyBaseChart';
interface Props {
lineChartData: LineChartEvolDataType
yAxisUnit: string
tooltipUnit?: string
chartId: string
isForHomePage?: boolean
min?: number
max?: number
title?: string
isLoading: boolean
setIsLoading: (loading: boolean) => void
numberDecimals?: number
}
export default function LineChart ({
lineChartData,
yAxisUnit,
isForHomePage = false,
numberDecimals,
min,
max,
chartId,
isLoading,
tooltipUnit,
setIsLoading,
title
}: Readonly<Props>): React.JSX.Element {
const options: Highcharts.Options = {
tooltip: {
shared: true,
shape: 'rect',
formatter ({ chart }) {
const points = chart.hoverPoints;
if (points?.[0].x) {
const element = React.createElement(LineChartTooltip, {
points,
x: parseFloat(points[0]?.x.toString()),
yAxisUnit: tooltipUnit ?? yAxisUnit,
numberDecimals
});
return ReactDOMServer.renderToString(element);
}
}
}
};
return (
<LazyBaseChart
chartType={ChartTypes.LINE}
chartData={lineChartData}
isLoading={isLoading}
setIsLoading={setIsLoading}
chartOptions={options}
chartId={chartId}
yAxisUnit={yAxisUnit}
title={title}
isForHomePage={isForHomePage}
/>
);
}
The LazyBaseChart is just the dynamic lazy loading form Nextjs used like this :
import dynamic from 'next/dynamic';
import Loader from '@/components/theme/atoms/Loader';
import React from 'react';
const LazyBaseChart = dynamic(
async () => await import('@/components/charts/common/BaseGraph'), {
ssr: false,
loading: () => <Loader />
});
export default LazyBaseChart;
I tried to optimize the performance of my Highcharts implementation by removing animations and some Highcharts modules such as the noData and accessibility modules. Despite these efforts, I am still encountering the Violation – message handler took xxms warnings.
The warnings seem to appear primarily when multiple charts (between 3 to 5) are rendered on a single page. When only one chart is displayed, the warning does not appear.
I have trouble understanding why these warnings appear and what I can do to fix them, could you please provide any insights on the subject ?
Any assistance or recommendations for improving performance and resolving these warnings would be greatly appreciated.
1