Server side rendering
ApexCharts fully supports server-side rendering for Next.js, Nuxt, SvelteKit, Astro, and other modern meta-frameworks. Charts rendered on the server produce static SVG that is immediately visible to users and web crawlers, with full interactivity restored on the client via hydration.
Package Entry Points
apexcharts/ssr covers both halves of the workflow: it renders charts on the server and hydrates them in the browser, so import it on both sides. The other two entries give the browser the default bundle, which has no SSR or hydration methods:
| Import | Environment | What it provides |
|---|---|---|
apexcharts/ssr | Node.js and browser | renderToString() and renderToHTML() on the server; hydrate(), hydrateAll() and isHydrated() in the browser |
apexcharts/client | Browser | The browser ESM build of the default bundle, with no SSR or hydration methods |
apexcharts | Node.js and browser | In Node.js it resolves to the SSR build. In a browser bundle it resolves to the default bundle, with no SSR or hydration methods |
The type definitions declare all five methods on the one ApexCharts class, so TypeScript accepts hydrate() on apexcharts/client or on apexcharts in browser code, and the call then fails in the browser with a hydrate is not a function TypeError.
In the browser apexcharts/ssr carries its own copy of the chart core. A page that also imports apexcharts, or an opt-in add-on such as apexcharts/sunburst (add-ons import apexcharts/core), ships a second copy.
Server-Side Rendering
Use renderToHTML() to generate hydration-ready HTML. It embeds the chart configuration in the output so the client can fully restore interactivity without re-fetching data.
// server.js (Node.js / any SSR framework)
import ApexCharts from 'apexcharts/ssr'
const html = await ApexCharts.renderToHTML(
{
series: [{ name: 'Sales', data: [30, 40, 35, 50, 49, 60, 70] }],
chart: { type: 'bar' },
xaxis: { categories: ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul'] }
},
{ width: 600, height: 350 }
)
// html is a self-contained string ready to embed in your page:
// <div class="apexcharts-ssr-wrapper" data-apexcharts-hydrate
// data-apexcharts-config="...">
// <svg ...>...</svg>
// </div>
renderToHTML() Options
| Option | Type | Default | Description |
|---|---|---|---|
width | number | 400 | Chart width in pixels |
height | number | 300 | Chart height in pixels |
scale | number | 1 | SVG scale factor (useful for high-DPI output) |
className | string | '' | Additional CSS class added to the wrapper div |
renderToString(): SVG Only
Use renderToString() when you only need the raw SVG string, for example to save to a file, pipe to a PDF renderer, or embed inside your own wrapper element.
import ApexCharts from 'apexcharts/ssr'
const svg = await ApexCharts.renderToString(
{
series: [{ data: [10, 41, 35, 51, 49, 62, 69, 91, 148] }],
chart: { type: 'line' }
},
{ width: 800, height: 400 }
)
// svg is a plain SVG string
// '<svg xmlns="http://www.w3.org/2000/svg" ...>...</svg>'
Chart Types and Features Outside the Default Bundle
apexcharts/ssr renders the chart types in the default bundle. Since 8.0 that bundle leaves out unit (with waffle and beeswarm), sunburst and violin, and the drilldown, waterfall, dumbbell and streamgraph features. Each takes the same side-effect import next to apexcharts/ssr that it takes next to apexcharts:
import ApexCharts from 'apexcharts/ssr'
import 'apexcharts/sunburst'
const html = await ApexCharts.renderToHTML(
{
chart: { type: 'sunburst' },
series: [{
data: [
{ x: 'Mobile', y: 55, children: [{ x: 'iOS', y: 30 }, { x: 'Android', y: 25 }] },
{ x: 'Desktop', y: 33, children: [{ x: 'Windows', y: 20 }, { x: 'macOS', y: 13 }] }
]
}]
},
{ width: 500, height: 500 }
)
A feature is apexcharts/features/<name>, for example apexcharts/features/waterfall. Icicle, raincloud and the features that were opt-in before 8.0 work the same way; the tree-shaking guide lists them. import 'apexcharts/full' next to apexcharts/ssr registers every chart type and feature at once.
On the server a missing chart type does not produce an empty chart. renderToHTML() and renderToString() reject with SSR rendering failed: followed by the line that names the import. A missing feature logs a warning and renders the chart without it.
Hydration builds the chart again in the browser from the embedded config, so the code that hydrates needs the same import next to apexcharts/ssr: import 'apexcharts/sunburst' in that module, or, when you load the hydration build lazily, await import('apexcharts/sunburst') before calling hydrate() or hydrateAll(). Without it the browser logs that the chart type is not registered and the chart keeps its static SVG.
Client-Side Hydration
Once the server-rendered HTML is in the browser, call hydrate() or hydrateAll() to restore full interactivity: tooltips, zoom, pan, legend toggling, and all other interactions.
// client.js (browser only)
import ApexCharts from 'apexcharts/ssr'
// Hydrate a single chart
const el = document.getElementById('my-chart')
const chart = ApexCharts.hydrate(el)
// Or hydrate every chart on the page at once
const charts = ApexCharts.hydrateAll()
Both return straight away, before the charts have rendered. To confirm that a chart is interactive, use isHydrated() or the apexcharts:hydrated event, both covered below.
Hydrate All on DOM Ready
import ApexCharts from 'apexcharts/ssr'
document.addEventListener('DOMContentLoaded', () => {
ApexCharts.hydrateAll()
})
hydrateAll() with a Custom Selector
By default hydrateAll() targets every element with data-apexcharts-hydrate. Pass a CSS selector to restrict hydration to a subset of elements.
import ApexCharts from 'apexcharts/ssr'
// Only hydrate charts inside the dashboard section
ApexCharts.hydrateAll('#dashboard [data-apexcharts-hydrate]')
Applying Client-Side Overrides on Hydration
Pass a clientOptions object as the second argument to override parts of the SSR configuration when hydrating. Common uses: tuning animations, adding event handlers, or changing theme for the interactive version.
import ApexCharts from 'apexcharts/ssr'
ApexCharts.hydrateAll('[data-apexcharts-hydrate]', {
chart: {
animations: { enabled: true, speed: 400 }
},
theme: { mode: 'dark' }
})
Hydration always turns animations on. In 8.0.0 it overrides chart.animations.enabled: false even when clientOptions sets it, so clientOptions can change animation settings such as speed but cannot switch animations off.
Checking Hydration State
isHydrated() returns true once the chart has finished rendering, just before the apexcharts:hydrated event below fires. Until then it returns false, including right after a hydrate() call.
import ApexCharts from 'apexcharts/ssr'
const el = document.getElementById('my-chart')
if (ApexCharts.isHydrated(el)) {
console.log('Chart is already interactive')
} else {
ApexCharts.hydrate(el)
}
Hydration Events
Each element dispatches an apexcharts:hydrated custom event when hydration completes successfully. Listen to it if you need to run code after the chart is interactive. The event does not bubble, so add the listener to the chart element itself.
This event and isHydrated() are the reliable checks. The array from hydrateAll() is not: it also holds an instance for a chart that fails to render, which leaves that element with its static SVG and logs ApexCharts hydration failed: in the console.
import ApexCharts from 'apexcharts/ssr'
const el = document.getElementById('my-chart')
el.addEventListener('apexcharts:hydrated', (e) => {
const { chart } = e.detail // the ApexCharts instance
console.log('Chart ready:', chart)
})
ApexCharts.hydrate(el)
Next.js Example
// app/page.jsx (Server Component)
import ApexCharts from 'apexcharts/ssr'
import ClientHydrator from './ClientHydrator'
export default async function Page() {
const chartHTML = await ApexCharts.renderToHTML(
{
series: [{ name: 'Monthly Revenue', data: [31, 40, 28, 51, 42, 109, 100] }],
chart: { type: 'area' },
xaxis: { categories: ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul'] }
},
{ width: 700, height: 350 }
)
return (
<main>
<h1>Dashboard</h1>
<div dangerouslySetInnerHTML={{ __html: chartHTML }} />
<ClientHydrator />
</main>
)
}
// app/ClientHydrator.jsx
'use client'
import { useEffect } from 'react'
export default function ClientHydrator() {
useEffect(() => {
let charts = []
let cancelled = false
// useEffect runs only in the browser, so the hydration build loads there
import('apexcharts/ssr').then(({ default: ApexCharts }) => {
if (!cancelled) charts = ApexCharts.hydrateAll()
})
return () => {
cancelled = true
charts.forEach((chart) => chart.destroy())
}
}, [])
return null
}
The cleanup destroys the charts when the component unmounts, and the cancelled flag covers an unmount that happens before the import resolves.
Nuxt Example
// server/api/chart.js
import ApexCharts from 'apexcharts/ssr'
export default defineEventHandler(async () => {
const html = await ApexCharts.renderToHTML(
{
series: [{ data: [10, 41, 35, 51, 49, 62, 69] }],
chart: { type: 'line' }
},
{ width: 600, height: 300 }
)
return { html }
})
<!-- pages/index.vue -->
<template>
<div v-html="chartHtml" />
</template>
<script setup>
const { data } = await useFetch('/api/chart')
const chartHtml = data.value.html
let charts = []
let unmounted = false
// onMounted runs only in the browser, so the hydration build loads there
onMounted(async () => {
const { default: ApexCharts } = await import('apexcharts/ssr')
if (!unmounted) charts = ApexCharts.hydrateAll()
})
onBeforeUnmount(() => {
unmounted = true
charts.forEach((chart) => chart.destroy())
})
</script>
vue3-apexcharts (<apexchart-hydrate>) and ng-apexcharts hydrate the same way: they load apexcharts/ssr with a dynamic import once the component is in the browser, and destroy the charts when it is removed. The Nuxt guide shows the components.
How the HTML Output Is Structured
renderToHTML() returns a wrapper div containing the static SVG and a base64-encoded copy of the chart configuration. The client uses the encoded config to reconstruct the fully interactive chart on hydration, no server round-trip needed.
<div class="apexcharts-ssr-wrapper"
data-apexcharts-hydrate
data-apexcharts-config="eyJzZXJpZXMiOi4uLn0=">
<svg xmlns="http://www.w3.org/2000/svg" ...>
<!-- full chart SVG -->
</svg>
</div>
After hydration, the element receives data-apexcharts-hydrated="true" and the data-apexcharts-config attribute is removed.