{"id":29840,"date":"2026-03-20T13:58:01","date_gmt":"2026-03-20T13:58:01","guid":{"rendered":"urn:uuid:cf2042db-a167-49a4-8742-7709b6221627"},"modified":"2026-04-23T07:33:23","modified_gmt":"2026-04-23T07:33:23","slug":"react-stock-chart-in-next-js-app-router-using-highcharts-stock-part-1","status":"publish","type":"post","link":"https:\/\/www.highcharts.com\/blog\/tutorials\/react-stock-chart-in-next-js-app-router-using-highcharts-stock-part-1\/","title":{"rendered":"React Stock Chart in Next.js (App Router) Using Highcharts Stock (part 1)"},"content":{"rendered":"<p>Building a <strong>react stock chart in Next.js<\/strong> is not the same as rendering a simple line chart in a client-only React app.<br \/>Next.js App Router enables Server Components by default. Stock charts, however, depend on browser APIs like window and document. If you render them on the server, you will hit errors such as:<\/p>\n<ul>\n<li>ReferenceError: window is not defined<\/li>\n<li>Hydration mismatches<\/li>\n<\/ul>\n<p>In this guide, I will build a clean, <strong>SSR-safe stock chart in Next.js App Route<\/strong>r using Highcharts Stock suitable for serious production React teams.<\/p>\n<h2><b>Why charts break in Next.js (SSR in 60 Seconds)<\/b><\/h2>\n<p><span style=\"font-weight: 400;\">Next.js App Router renders components on the server unless explicitly marked as client components.<\/span><\/p>\n<p><span style=\"font-weight: 400;\">Server environments do not have access to:<\/span><\/p>\n<ul>\n<li style=\"font-weight: 400;\" aria-level=\"1\"><span style=\"font-weight: 400;\">window<\/span><\/li>\n<li style=\"font-weight: 400;\" aria-level=\"1\"><span style=\"font-weight: 400;\">document<\/span><\/li>\n<li style=\"font-weight: 400;\" aria-level=\"1\"><span style=\"font-weight: 400;\">Layout measurement APIs<\/span><\/li>\n<\/ul>\n<p><span style=\"font-weight: 400;\">Highcharts relies on these browser APIs to render SVG elements and calculate chart layout. <\/span><span style=\"font-weight: 400;\">Therefore, s<\/span><span style=\"font-weight: 400;\">tock charts must be rendered only on the client in Next.js, and t<\/span><span style=\"font-weight: 400;\">he correct solution is simple: isolate the chart inside a Client Component using <code>'use client'<\/code>.<\/span><\/p>\n<h2><b>The correct client-only pattern<\/b><\/h2>\n<p><span style=\"font-weight: 400;\">In Next.js App Router:<\/span><\/p>\n<ul>\n<li style=\"font-weight: 400;\" aria-level=\"1\"><span style=\"font-weight: 400;\">page.tsx is a Server Component by default.<\/span><\/li>\n<li style=\"font-weight: 400;\" aria-level=\"1\"><span style=\"font-weight: 400;\">Chart components must live inside a Client Component.<\/span><\/li>\n<\/ul>\n<p><span style=\"font-weight: 400;\">Create a dedicated client component:<\/span><\/p>\n<p><span style=\"font-weight: 400;\"><\/p>\n<pre><code>'use client'<\/code><\/pre>\n<p><\/span><\/p>\n<h2><b>The correct client-only pattern<\/b><\/h2>\n<p><span style=\"font-weight: 400;\">In Next.js App Router:<\/span><\/p>\n<ul>\n<li style=\"font-weight: 400;\" aria-level=\"1\"><span style=\"font-weight: 400;\">page.tsx is a Server Component by default.<\/span><\/li>\n<li style=\"font-weight: 400;\" aria-level=\"1\"><span style=\"font-weight: 400;\">Chart components must live inside a client component.<\/span><\/li>\n<\/ul>\n<p><span style=\"font-weight: 400;\">Create a dedicated client component, then render Highcharts inside it. This ensures:<\/span><\/p>\n<ul>\n<li style=\"font-weight: 400;\" aria-level=\"1\"><span style=\"font-weight: 400;\">No SSR crashes<\/span><\/li>\n<li style=\"font-weight: 400;\" aria-level=\"1\"><span style=\"font-weight: 400;\">No hydration errors<\/span><\/li>\n<li style=\"font-weight: 400;\" aria-level=\"1\"><span style=\"font-weight: 400;\">Clean separation of server and client logic<\/span><\/li>\n<\/ul>\n<h2><b>React Stock Chart in Next.js<\/b><\/h2>\n<p><span style=\"font-weight: 400;\">Now, let&#8217;s take a look at a complete, production-safe example using Highcharts Stock with:<\/span><\/p>\n<ul>\n<li style=\"font-weight: 400;\" aria-level=\"1\"><span style=\"font-weight: 400;\">Percent comparison<\/span><\/li>\n<li style=\"font-weight: 400;\" aria-level=\"1\"><span style=\"font-weight: 400;\">Navigator panel<\/span><\/li>\n<li style=\"font-weight: 400;\" aria-level=\"1\"><span style=\"font-weight: 400;\">Range selector<\/span><\/li>\n<li style=\"font-weight: 400;\" aria-level=\"1\"><span style=\"font-weight: 400;\">Multiple series (MSFT, AAPL, GOOG)<\/span><\/li>\n<\/ul>\n<p><span style=\"font-weight: 400;\">Feel free to check:<br \/><a href=\"https:\/\/dr26pz-3000.csb.app\/\">Link to the live demo\u00a0 (React Stock Chart in Next.js (App Router) Using Highcharts Stock)<\/a><br \/><\/span><a href=\"https:\/\/codesandbox.io\/p\/github\/mekhatria\/React-Stock-Chart-Nextjs-Highcharts-Stock-part-1\/main\">Link to <span style=\"font-weight: 400;\">CodeSandBox (React Stock Chart in Next.js (App Router) Using Highcharts Stock)<\/span><\/a><\/p>\n<p><iframe style=\"width: 100%; height: 520px; border: 0; border-radius: 4px; overflow: hidden;\" title=\"React Stock Chart\" src=\"https:\/\/dr26pz-3000.csb.app\/\" sandbox=\"allow-forms allow-modals allow-popups allow-presentation allow-same-origin allow-scripts\"><\/iframe><\/p>\n\n\n<p><br>You can also take a look at the code right here \ud83d\ude42 <br><\/p>\n\n\n<pre class=\"wp-block-code\"><code>\n'use client';\nimport React, { useEffect, useMemo, useState } from 'react';\nimport type Highcharts from 'highcharts';\nimport { StockChart } from '@highcharts\/react\/Stock';\nimport { LineSeries } from '@highcharts\/react\/series\/Line';\nimport { Title } from '@highcharts\/react\/options';\nimport { Accessibility } from '@highcharts\/react\/options\/Accessibility';\ntype SeriesLike = {\n  name: string;\n  data: [number, number][];\n};\nconst NAMES = ['MSFT', 'AAPL', 'GOOG'] as const;\nexport default function StockCompareChart() {\n  const [series, setSeries] = useState<SeriesLike[] | null>(null);\n  useEffect(() => {\n    (async () => {\n      const results: SeriesLike[] = [];\n      for (const name of NAMES) {\n        const response = await fetch(\n          `https:\/\/cdn.jsdelivr.net\/gh\/highcharts\/highcharts@f0e61a1\/samples\/data\/${name.toLowerCase()}-c.json`\n        );\n        const data = await response.json();\n        results.push({ name, data });\n      }\n      setSeries(results);\n    })();\n  }, []);\n  const options = useMemo<Highcharts.Options>(() => ({\n    rangeSelector: { selected: 4 },\n    yAxis: {\n      labels: {\n        format: '{#if (gt value 0)}+{\/if}{value}%'\n      }\n    },\n    plotOptions: {\n      series: {\n        compare: 'percent',\n        showInNavigator: true,\n        turboThreshold: 0\n      }\n    }\n  }), []);\nif (!series) return &lt;div&gt;Loading stock data...&lt;\/div&gt;;\nreturn (\n  &lt;StockChart options={options}&gt;\n    &lt;Title&gt;MSFT vs AAPL vs GOOG (Percent Comparison)&lt;\/Title&gt;\n    &lt;Accessibility enabled \/&gt;\n    {series.map((s) =&gt; (\n      &lt;LineSeries key={s.name} name={s.name} data={s.data} \/&gt;\n    ))}\n  &lt;\/StockChart&gt;\n);\n}<\/code><\/pre>\n\n\n<p><\/p>\n\n\n\n<p>This example:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>Avoids SSR execution<\/li>\n\n\n\n<li>Loads data client-side<\/li>\n\n\n\n<li>Uses built-in percent comparison<\/li>\n\n\n\n<li>Enables the navigator panel<\/li>\n\n\n\n<li>Handles larger datasets via <code>turboThreshold: 0<\/code><\/li>\n<\/ul>\n\n\n\n<p><\/p>\n\n\n\n<h2 class=\"wp-block-heading\"><strong>A quick performance note<\/strong><\/h2>\n\n\n\n<p>Highcharts Stock is designed for time-series and financial data. so to ensure consistent rendering behavior even with large datasets use:<\/p>\n\n\n\n<p>turboThreshold: 0<\/p>\n\n\n\n<h2 class=\"wp-block-heading\"><strong>When to use Highcharts Stock in Next.js<\/strong><\/h2>\n\n\n\n<p>I want you to understand that many React chart libraries work well for simple dashboards. But, serious financial or analytics applications often require:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>Built-in range selectors<\/li>\n\n\n\n<li>Percent comparison between assets<\/li>\n\n\n\n<li>Navigator panels<\/li>\n\n\n\n<li>Smooth handling of historical time-series data<\/li>\n<\/ul>\n\n\n\n<p>Highcharts Stock provides these capabilities out of the box, while remaining compatible with modern Next.js App Router patterns.<\/p>\n\n\n\n<p>With a clear client boundary, it integrates cleanly into production-grade React applications.<\/p>\n\n\n\n<p>In the second part I will show you how to improve performance and enhance UX.<\/p>\n\n\n\n<p><\/p>\n\n\n\n<p><\/p>\n","protected":false},"excerpt":{"rendered":"<p>Building a react stock chart in Next.js is not the same as rendering a simple line chart in a client-only React app.Next.js App Router enables Server Components by default. Stock charts, however, depend on browser APIs like window and document. If you render them on the server, you will hit errors such as: ReferenceError: window [&hellip;]<\/p>\n","protected":false},"author":32,"featured_media":29965,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"meta_title":"React Stock Chart in Next.js App Router Using Highcharts Stock","meta_description":"Learn how to build a SSR-safe React stock chart in Next.js App Router using Highcharts Stock. Includes a clean client-only setup, percent comparison, and production-ready integration tips","hc_selected_options":[],"footnotes":""},"categories":[210],"tags":[883,1107,824],"coauthors":[699],"class_list":["post-29840","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-tutorials","tag-highcharts-stock","tag-next-js","tag-react"],"_links":{"self":[{"href":"https:\/\/www.highcharts.com\/blog\/wp-json\/wp\/v2\/posts\/29840","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/www.highcharts.com\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/www.highcharts.com\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/www.highcharts.com\/blog\/wp-json\/wp\/v2\/users\/32"}],"replies":[{"embeddable":true,"href":"https:\/\/www.highcharts.com\/blog\/wp-json\/wp\/v2\/comments?post=29840"}],"version-history":[{"count":4,"href":"https:\/\/www.highcharts.com\/blog\/wp-json\/wp\/v2\/posts\/29840\/revisions"}],"predecessor-version":[{"id":30031,"href":"https:\/\/www.highcharts.com\/blog\/wp-json\/wp\/v2\/posts\/29840\/revisions\/30031"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/www.highcharts.com\/blog\/wp-json\/wp\/v2\/media\/29965"}],"wp:attachment":[{"href":"https:\/\/www.highcharts.com\/blog\/wp-json\/wp\/v2\/media?parent=29840"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.highcharts.com\/blog\/wp-json\/wp\/v2\/categories?post=29840"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.highcharts.com\/blog\/wp-json\/wp\/v2\/tags?post=29840"},{"taxonomy":"author","embeddable":true,"href":"https:\/\/www.highcharts.com\/blog\/wp-json\/wp\/v2\/coauthors?post=29840"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}