All posts

Use a React Candlestick Chart in Next.js

Build a Next.js trading chart with candlesticks, volume, SMA overlays, a Stochastic indicator, and light and dark themes.

This guide shows how to use react-candlesticks in a Next.js project. You will build an interactive candlestick chart with a price header, volume panel, SMA 20 and SMA 50 overlays, and a Stochastic indicator panel.

React candlestick chart in Next.js with volume, SMA overlays, and a Stochastic indicator
A React candlestick chart in Next.js with volume, SMA overlays, and Stochastic.

You can try the live CodeSandbox demo, browse the GitHub example, run it locally, or copy the files below into a Next.js app.

What you will build

The example keeps the page structure small and puts the trading UI into one chart component.

  • A React candlestick chart in Next.js
  • A compact symbol, price, and change header
  • Light and dark theme controls
  • Toggleable volume and indicator panels
  • SMA 20 and SMA 50 overlays
  • A Stochastic oscillator panel

Install dependencies

npm install next@15 react react-dom react-candlesticks

Import the chart stylesheet

Import the package stylesheet from the root layout so it is available wherever the chart renders.

import 'react-candlesticks/style.css';
import './globals.css';

export default function RootLayout({
  children,
}: Readonly<{
  children: React.ReactNode;
}>) {
  return (
    <html lang="en">
      <body>{children}</body>
    </html>
  );
}

Keep the page server-rendered

The page imports a client loader, but the page itself does not need 'use client'. It can stay focused on the surrounding layout while the chart component owns chart state and browser rendering.

import ChartLoader from './components/ChartLoader';

export default function Home() {
  return (
    <main className="page-shell">
      <section className="intro">
        <h1>React Candlestick Chart in Next.js</h1>
        <p>
          An interactive candlestick chart with volume, SMA overlays, and a
          Stochastic indicator, built for a Next.js trading dashboard.
        </p>
      </section>

      <section className="dashboard" aria-label="Trading chart">
        <ChartLoader />
      </section>
    </main>
  );
}

Load the chart only in the browser

Create a small Client Component that uses next/dynamic. The ssr: false option tells Next.js not to prerender the chart component on the server.

'use client';

import dynamic from 'next/dynamic';

const CandlestickChart = dynamic(() => import('./CandlestickChart'), {
  ssr: false,
  loading: () => <div className="chart-loading">Loading chart...</div>,
});

export default function ChartLoader() {
  return <CandlestickChart />;
}

Render the candlestick chart

The actual chart component is also a Client Component. It can use React state for controls and render react-candlesticks layers normally.

'use client';

import { useState } from 'react';
import {
  Candlesticks,
  Chart,
  Panel,
  SMA,
  Stochastic,
  VolumeBars,
  exampleData,
} from 'react-candlesticks';

type ChartTheme = 'light' | 'dark';

export default function CandlestickChart() {
  const [theme, setTheme] = useState<ChartTheme>('light');
  const [showVolume, setShowVolume] = useState(true);
  const [showIndicators, setShowIndicators] = useState(true);

  const latest = exampleData[exampleData.length - 1];
  const previous = exampleData[exampleData.length - 2];
  const change = latest.close - previous.close;
  const changePercent = (change / previous.close) * 100;

  return (
    <div className={`chart-card chart-card--${theme}`}>
      <div className="chart-toolbar">
        <div className="chart-quote">
          <span className="chart-symbol">TSLA</span>
          <span className="chart-price">{latest.close.toFixed(2)}</span>
          <span className={change >= 0 ? 'change change--up' : 'change change--down'}>
            {change >= 0 ? '+' : ''}
            {change.toFixed(2)} ({changePercent.toFixed(2)}%)
          </span>
        </div>
        <div className="chart-actions" aria-label="Chart controls">
          <button
            type="button"
            className={theme === 'dark' ? 'is-active' : ''}
            onClick={() => setTheme('dark')}
          >
            Dark
          </button>
          <button
            type="button"
            className={theme === 'light' ? 'is-active' : ''}
            onClick={() => setTheme('light')}
          >
            Light
          </button>
          <label>
            <input
              type="checkbox"
              checked={showVolume}
              onChange={(event) => setShowVolume(event.target.checked)}
            />
            Volume
          </label>
          <label>
            <input
              type="checkbox"
              checked={showIndicators}
              onChange={(event) => setShowIndicators(event.target.checked)}
            />
            Indicators
          </label>
        </div>
      </div>

      <div className="chart-frame">
        <Chart
          data={exampleData}
          granularity="d1"
          initialScrollToLatest
          theme={theme}
        >
          <Panel heightRatio={3}>
            <Candlesticks />
            {showIndicators ? (
              <SMA period={20} series={{ value: { color: '#777' } }} />
            ) : null}
            {showIndicators ? <SMA period={50} /> : null}
          </Panel>
          {showVolume ? (
            <Panel>
              <VolumeBars />
            </Panel>
          ) : null}
          {showIndicators ? (
            <Panel>
              <Stochastic />
            </Panel>
          ) : null}
        </Chart>
      </div>
    </div>
  );
}

Give the chart a real height

The chart fills its container, so the container needs measurable dimensions. In a dashboard, a fixed or responsive height is usually the most predictable option.

.chart-frame {
  height: 600px;
  min-height: 420px;
  overflow: hidden;
}

.chart-loading {
  display: grid;
  height: 600px;
  place-items: center;
}

@media (max-width: 820px) {
  .chart-frame,
  .chart-loading {
    height: 520px;
  }
}

What to change for real market data

This example uses exampleData so it runs without credentials or API setup. In production, fetch OHLCV data in a Server Component, route handler, or your backend, then pass a serializable DataPoint[] into the client chart. Keep functions, class instances, and non-serializable values out of props that cross the server-client boundary.

That gives the chart a clean boundary: Next.js handles the application shell, while react-candlesticks handles Canvas rendering, panels, layers, legends, crosshairs, zoom, and pan interactions.