{"id":30628,"date":"2026-05-21T13:25:46","date_gmt":"2026-05-21T13:25:46","guid":{"rendered":"urn:uuid:562e6179-d06a-4610-86ba-cf649df02270"},"modified":"2026-05-21T13:25:47","modified_gmt":"2026-05-21T13:25:47","slug":"build-a-single-page-application-with-highcharts-react","status":"publish","type":"post","link":"https:\/\/www.highcharts.com\/blog\/tutorials\/build-a-single-page-application-with-highcharts-react\/","title":{"rendered":"Build a Single Page Application with Highcharts React"},"content":{"rendered":"\n<div class=\"wp-block-group is-vertical is-layout-flex wp-container-core-group-is-layout-7f6e81b6 wp-block-group-is-layout-flex\" style=\"margin-bottom:2rem\">\n<div class=\"wp-block-group is-vertical is-layout-flex wp-container-core-group-is-layout-355c4241 wp-block-group-is-layout-flex\">\n<p>In this tutorial we will build a simple React web app that fetches data from static JSON files and uses that data to create a chart using Highcharts React. We are doing this to mimic fetching data from the server in a Single Page Application (SPA) setting.<\/p>\n\n\n\n<p>We will learn what an SPA actually is, why on-demand data fetching is a defining feature, and how Highcharts fits into that pattern.<\/p>\n<\/div>\n<\/div>\n\n\n\n<div class=\"wp-block-group is-vertical is-layout-flex wp-container-core-group-is-layout-7f6e81b6 wp-block-group-is-layout-flex\" style=\"margin-bottom:2rem\">\n<h2 class=\"wp-block-heading\">What is A Single Page Application?<\/h2>\n\n\n\n<div class=\"wp-block-group is-vertical is-layout-flex wp-container-core-group-is-layout-355c4241 wp-block-group-is-layout-flex\">\n<p>In a traditional website, every time you click a link or submit a form, the browser makes a request to the server, which sends back a brand new HTML page. This means that the current page is discarded and the new one is rendered from scratch.<\/p>\n\n\n\n<p>An SPA works differently. The browser first loads the web page, but from that point on, the page never fully reloads. When the user interacts with the app, JavaScript handles the updates needed for the resulting view with minimal server-side involvement. The general idea is to keep as much as possible on the client side, and fetch only the data that is needed to load the new page view.<\/p>\n\n\n\n<p>This is what keeps SPAs responsive: <strong>you don\u2019t reload the page, you only fetch what you need, when you need it.<\/strong><\/p>\n<\/div>\n<\/div>\n\n\n\n<div class=\"wp-block-group is-vertical is-layout-flex wp-container-core-group-is-layout-7f6e81b6 wp-block-group-is-layout-flex\" style=\"margin-bottom:2rem\">\n<h2 class=\"wp-block-heading\">How Highcharts Fits In<\/h2>\n\n\n\n<div class=\"wp-block-group is-vertical is-layout-flex wp-container-core-group-is-layout-355c4241 wp-block-group-is-layout-flex\">\n<p>Highcharts is a client-side charting library that renders interactive charts directly in the browser, which makes it a natural fit for SPAs where charts update without reloading the page.<\/p>\n\n\n\n<p>In an SPA, Highcharts is loaded along with the rest of your app on initial page load. When new data arrives from an API, you pass it to your chart component and it updates instantly. No need for a page reload.<\/p>\n\n\n\n<p>In this tutorial we will build a simple demo that demonstrates this pattern:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li class=\"has-font-size-300-font-size\" style=\"margin-top:0;margin-bottom:var(--wp--preset--spacing--20)\">The app with a line chart loads once in the browser<\/li>\n\n\n\n<li class=\"has-font-size-300-font-size\" style=\"margin-top:0;margin-bottom:var(--wp--preset--spacing--20)\">The user selects a year<\/li>\n\n\n\n<li class=\"has-font-size-300-font-size\" style=\"margin-top:0;margin-bottom:var(--wp--preset--spacing--20)\">The app fetches only the data for that year<\/li>\n\n\n\n<li class=\"has-font-size-300-font-size\" style=\"margin-top:0;margin-bottom:var(--wp--preset--spacing--20)\">Highcharts renders the new data by updating the line chart, without reloading the page<\/li>\n<\/ul>\n\n\n\n<p><em><strong>Note:<\/strong> For this small demo, it would make more sense to load all data upfront. The point here is to demonstrate a dynamic fetching pattern you can use in larger applications with big datasets, real-time updates, server-side filtering, or multiple independent data sources.<\/em><\/p>\n<\/div>\n<\/div>\n\n\n\n<div class=\"wp-block-group is-vertical is-layout-flex wp-container-core-group-is-layout-7f6e81b6 wp-block-group-is-layout-flex\" style=\"margin-bottom:2rem\">\n<h2 class=\"wp-block-heading\">What We Will Build<\/h2>\n\n\n\n<div class=\"wp-block-group is-vertical is-layout-flex wp-container-core-group-is-layout-355c4241 wp-block-group-is-layout-flex\">\n<p style=\"margin-top:var(--wp--preset--spacing--20);margin-bottom:0\">We will use Vite to build a single-page React app showing monthly website traffic data (Organic, Direct, and Referral visits) for the years 2025, 2024, and 2023. The user will select the year by clicking on buttons above the chart, after which the chart will update to show the corresponding data. Yearly data will be fetched over HTTP from static JSON files. We will mimic real-life network latency by adding an artificial delay to the data fetching. A loading spinner will be shown while the data arrives, making the fetch-load-render cycle visible.<\/p>\n\n\n\n<p><br><strong>Live Demo:<\/strong> <a href=\"https:\/\/stackblitz.com\/edit\/vitejs-vite-burfgft5\">Open in Stackblitz<\/a><\/p>\n\n\n\n<div class=\"wp-block-highsoft-hs-image has-dark-mode-image\" data-dark-src=\"https:\/\/www.highcharts.com\/blog\/wp-content\/uploads\/2026\/05\/Screenshot-2026-05-21-at-12.41.56-1.jpg\" data-dark-srcset=\"https:\/\/www.highcharts.com\/blog\/wp-content\/uploads\/2026\/05\/Screenshot-2026-05-21-at-12.41.56-1-70x60.jpg 70w, https:\/\/www.highcharts.com\/blog\/wp-content\/uploads\/2026\/05\/Screenshot-2026-05-21-at-12.41.56-1-560x386.jpg 560w, https:\/\/www.highcharts.com\/blog\/wp-content\/uploads\/2026\/05\/Screenshot-2026-05-21-at-12.41.56-1-760x524.jpg 760w, https:\/\/www.highcharts.com\/blog\/wp-content\/uploads\/2026\/05\/Screenshot-2026-05-21-at-12.41.56-1.jpg 1160w\" data-dark-sizes=\"(max-width: 1160px) 100vw, 1160px\" data-has-dark=\"true\">\n<figure class=\"wp-block-image size-large has-custom-border is-style-default\"><img loading=\"lazy\" decoding=\"async\" width=\"760\" height=\"524\" src=\"https:\/\/www.highcharts.com\/blog\/wp-content\/uploads\/2026\/05\/Screenshot-2026-05-21-at-12.41.56-760x524.jpg\" alt=\"Screenshot of the finished app: an area spline chart showing monthly Organic, Direct, and Referral website traffic for 2025, with year selector buttons (2023, 2024, 2025) above the chart.\" class=\"wp-image-30658\" style=\"border-top-left-radius:0px;border-top-right-radius:0px;border-bottom-left-radius:0px;border-bottom-right-radius:0px\" srcset=\"https:\/\/www.highcharts.com\/blog\/wp-content\/uploads\/2026\/05\/Screenshot-2026-05-21-at-12.41.56-760x524.jpg 760w, https:\/\/www.highcharts.com\/blog\/wp-content\/uploads\/2026\/05\/Screenshot-2026-05-21-at-12.41.56-560x386.jpg 560w, https:\/\/www.highcharts.com\/blog\/wp-content\/uploads\/2026\/05\/Screenshot-2026-05-21-at-12.41.56-768x530.jpg 768w, https:\/\/www.highcharts.com\/blog\/wp-content\/uploads\/2026\/05\/Screenshot-2026-05-21-at-12.41.56.jpg 1160w\" sizes=\"auto, (max-width: 760px) 100vw, 760px\" \/><\/figure>\n<\/div>\n\n\n\n<p><strong>Tech stack:<\/strong><\/p>\n\n\n\n<ul style=\"margin-top:0;margin-bottom:0\" class=\"wp-block-list has-font-size-300-font-size\">\n<li style=\"margin-top:0\">Vite 8 &#8211; fast development build tool<\/li>\n\n\n\n<li style=\"margin-top:var(--wp--preset--spacing--20)\">React 19 + TypeScript &#8211; UI layer with type safety<\/li>\n\n\n\n<li style=\"margin-top:var(--wp--preset--spacing--20)\">@highcharts\/react v5.0 &#8211; declarative Highcharts React integration<\/li>\n\n\n\n<li style=\"margin-top:var(--wp--preset--spacing--20)\">Vite public assets &#8211; static JSON files served as-is over HTTP<\/li>\n<\/ul>\n\n\n\n<p><em><strong>A note on this setup:<\/strong> to simplify things, we are using static JSON files to keep the focus entirely on the fetching and rendering pattern. In a production SPA, you would fetch from a real API endpoint. However, the data fetching pattern remains the same regardless of where the data comes from.<\/em><\/p>\n<\/div>\n<\/div>\n\n\n\n<div class=\"wp-block-group is-vertical is-layout-flex wp-container-core-group-is-layout-7f6e81b6 wp-block-group-is-layout-flex\" style=\"margin-bottom:2rem\">\n<h2 class=\"wp-block-heading\">Prerequisites<\/h2>\n\n\n\n<div class=\"wp-block-group is-vertical is-layout-flex wp-container-core-group-is-layout-355c4241 wp-block-group-is-layout-flex\">\n<p>Before you start, make sure you have the following:<\/p>\n\n\n\n<ul style=\"margin-top:0;margin-bottom:0\" class=\"wp-block-list has-font-size-300-font-size\">\n<li style=\"margin-top:var(--wp--preset--spacing--20)\">Basic familiarity with React (components, props, state)<\/li>\n\n\n\n<li style=\"margin-top:var(--wp--preset--spacing--20)\">Node.js 18 or later installed (includes npm)<\/li>\n\n\n\n<li style=\"margin-top:var(--wp--preset--spacing--20)\">A modern browser<\/li>\n\n\n\n<li style=\"margin-top:var(--wp--preset--spacing--20)\">A code editor (VS Code recommended)<\/li>\n<\/ul>\n<\/div>\n<\/div>\n\n\n\n<div class=\"wp-block-group is-vertical is-layout-flex wp-container-core-group-is-layout-b45bb31d wp-block-group-is-layout-flex\" style=\"margin-top:0;margin-bottom:2rem\">\n<h2 class=\"wp-block-heading\">Building the App<\/h2>\n\n\n\n<div class=\"wp-block-group is-vertical is-layout-flex wp-container-core-group-is-layout-134f5aa0 wp-block-group-is-layout-flex\" style=\"margin-top:0;margin-bottom:0rem;padding-top:0;padding-bottom:0\">\n<div class=\"wp-block-group is-vertical is-layout-flex wp-container-core-group-is-layout-fe9cc265 wp-block-group-is-layout-flex\">\n<h3 class=\"wp-block-heading\" style=\"margin-bottom:var(--wp--preset--spacing--50)\">Step 1: Scaffold the Project<\/h3>\n\n\n\n<div class=\"wp-block-group is-vertical is-layout-flex wp-container-core-group-is-layout-c35747d5 wp-block-group-is-layout-flex\">\n<p style=\"margin-bottom:0\">Open your terminal in a new folder with a name of your choosing and run:<\/p>\n\n\n\n<div class=\"hs-code-outer-container\"><div class=\"hs-code-container neutral-50-light neutral-800-dark\" role=\"region\" aria-label=\"Code block\">\n\n<pre class=\"wp-block-code\"><code>npm create vite@latest<\/code><\/pre>\n\n\n<div class=\"wp-block-highsoft-hs-button\"><button type=\"button\" class=\"hc-button hc-button--white hc-button--size-200\" data-copy-code=\"true\" aria-label=\"Copy\">Copy<\/button><\/div>\n\n<\/div><\/div>\n\n\n\n<div class=\"wp-block-group is-vertical is-layout-flex wp-container-core-group-is-layout-08768d6f wp-block-group-is-layout-flex\" style=\"margin-top:0;margin-bottom:0;padding-top:0;padding-bottom:0\">\n<p style=\"margin-top:0\">In the following prompts select <code>.<\/code> as the project name to use the current folder. Select <code>React<\/code> as the framework and <code>TypeScript<\/code> as the variant.<\/p>\n\n\n\n<p><em><strong>Note:<\/strong> vite@latest currently resolves to Vite 8. The tutorial assumes this version.<\/em><\/p>\n\n\n\n<p>When prompted &#8220;Install with npm and start now?&#8221; select <strong>Yes<\/strong>. This will automatically install dependencies and start the Vite dev server on port 5173.<\/p>\n\n\n\n<p style=\"margin-bottom:0\">If you close the terminal and come back to the project later, restart the dev server with:<\/p>\n<\/div>\n\n\n\n<div class=\"hs-code-outer-container\"><div class=\"hs-code-container neutral-50-light neutral-800-dark\" role=\"region\" aria-label=\"Code block\">\n\n<pre class=\"wp-block-code\"><code>npm run dev<\/code><\/pre>\n\n\n<div class=\"wp-block-highsoft-hs-button\"><button type=\"button\" class=\"hc-button hc-button--white hc-button--size-200\" data-copy-code=\"true\" aria-label=\"Copy\">Copy<\/button><\/div>\n\n<\/div><\/div>\n<\/div>\n<\/div>\n\n\n\n<div class=\"wp-block-group is-vertical is-layout-flex wp-container-core-group-is-layout-3210c5b8 wp-block-group-is-layout-flex\" style=\"margin-top:var(--wp--preset--spacing--40)\">\n<h3 class=\"wp-block-heading\" style=\"margin-bottom:var(--wp--preset--spacing--50)\">Step 2: Clean Up Template Files<\/h3>\n\n\n\n<div class=\"wp-block-group is-vertical is-layout-flex wp-container-core-group-is-layout-c35747d5 wp-block-group-is-layout-flex\">\n<p>Vite scaffolded a demo app with files we don&#8217;t need. Delete these:<\/p>\n\n\n\n<ul class=\"wp-block-list has-font-size-300-font-size\">\n<li>Contents of the <code>public\/<\/code> folder. Keep the folder itself, we will use it for data files<\/li>\n\n\n\n<li><code>assets\/<\/code> folder from the <code>src\/<\/code> directory<\/li>\n<\/ul>\n\n\n\n<p>Leave <code>App.tsx<\/code> in place. We will replace the contents later. For <code>App.css<\/code> and <code>index.css<\/code>, copy the versions from the Stackblitz demo above into <code>src\/<\/code>. This tutorial is not about CSS, so we won&#8217;t cover styling in detail.<\/p>\n<\/div>\n<\/div>\n\n\n\n<div class=\"wp-block-group is-vertical is-layout-flex wp-container-core-group-is-layout-3210c5b8 wp-block-group-is-layout-flex\" style=\"margin-top:var(--wp--preset--spacing--40)\">\n<h3 class=\"wp-block-heading\" style=\"margin-bottom:var(--wp--preset--spacing--50)\">Step 3: Install Highcharts React<\/h3>\n\n\n\n<div class=\"wp-block-group is-vertical is-layout-flex wp-container-core-group-is-layout-c35747d5 wp-block-group-is-layout-flex\">\n<p style=\"margin-bottom:0\">Next, let&#8217;s install Highcharts React from npm:<\/p>\n\n\n\n<div class=\"hs-code-outer-container\"><div class=\"hs-code-container neutral-50-light neutral-800-dark\" role=\"region\" aria-label=\"Code block\">\n\n<pre class=\"wp-block-code\"><code>npm install @highcharts\/react<\/code><\/pre>\n\n\n<div class=\"wp-block-highsoft-hs-button\"><button type=\"button\" class=\"hc-button hc-button--white hc-button--size-200\" data-copy-code=\"true\" aria-label=\"Copy\">Copy<\/button><\/div>\n\n<\/div><\/div>\n<\/div>\n<\/div>\n\n\n\n<div class=\"wp-block-group is-vertical is-layout-flex wp-container-core-group-is-layout-3210c5b8 wp-block-group-is-layout-flex\" style=\"margin-top:var(--wp--preset--spacing--40)\">\n<h3 class=\"wp-block-heading\" style=\"margin-bottom:var(--wp--preset--spacing--50)\">Step 4: Create the Data Files<\/h3>\n\n\n\n<div class=\"wp-block-group is-vertical is-layout-flex wp-container-core-group-is-layout-9a3dfb98 wp-block-group-is-layout-flex\" style=\"margin-top:0;margin-bottom:0\">\n<p style=\"margin-bottom:0\">Instead of a separate API server, we&#8217;ll serve data as plain JSON files from Vite&#8217;s <code>public\/<\/code> directory. Vite automatically makes everything inside public\/ available at the root URL, so <code>public\/data\/traffic-XXXX.json<\/code> is reachable at <code>\/data\/traffic-XXXX.json<\/code>.<br>Create <code>public\/data\/traffic-2025.json<\/code>:<\/p>\n\n\n\n<div class=\"hs-code-outer-container\"><div class=\"hs-code-container neutral-50-light neutral-800-dark\" role=\"region\" aria-label=\"Code block\">\n\n<pre class=\"wp-block-code\"><code>{\n    \"id\": \"2025\",\n    \"year\": 2025,\n    \"series\": &#091;\n        {\n            \"name\": \"Organic\",\n            \"data\": &#091;\n                8423, 9234, 10456, 9845, 12123, 14567, 16234, 15123, 13456,\n                11234, 10345, 9123\n            ]\n        },\n        {\n            \"name\": \"Direct\",\n            \"data\": &#091;\n                5234, 5834, 6567, 6789, 8012, 8945, 10123, 9234, 8567, 6456,\n                5234, 5123\n            ]\n        },\n        {\n            \"name\": \"Referral\",\n            \"data\": &#091;\n                1867, 2145, 2734, 2956, 3234, 3789, 4123, 3845, 3234, 2567,\n                2145, 1945\n            ]\n        }\n    ]\n}<\/code><\/pre>\n\n\n<div class=\"wp-block-highsoft-hs-button\"><button type=\"button\" class=\"hc-button hc-button--white hc-button--size-200\" data-copy-code=\"true\" aria-label=\"Copy\">Copy<\/button><\/div>\n\n<\/div><\/div>\n\n\n\n<p style=\"margin-top:0\">Next, create the equivalent data files for 2024 and 2023. We haven&#8217;t included all the contents here, but just copy the JSON files from the Stackblitz repo linked to above.<\/p>\n\n\n\n<p>These files will be our entire data layer. The Vite dev server will serve them over HTTP, and when we build for production, it is copied into the output folder alongside the app.<\/p>\n<\/div>\n<\/div>\n\n\n\n<div class=\"wp-block-group is-vertical is-layout-flex wp-container-core-group-is-layout-3210c5b8 wp-block-group-is-layout-flex\" style=\"margin-top:var(--wp--preset--spacing--40)\">\n<h3 class=\"wp-block-heading\" style=\"margin-bottom:var(--wp--preset--spacing--50)\">Step 5: Define Types<\/h3>\n\n\n\n<div class=\"wp-block-group is-vertical is-layout-flex wp-container-core-group-is-layout-9a3dfb98 wp-block-group-is-layout-flex\" style=\"margin-top:0;margin-bottom:0\">\n<p style=\"margin-bottom:0\">Create <code>src\/types.ts<\/code> in the <code>src<\/code> folder:<\/p>\n\n\n\n<div class=\"hs-code-outer-container\"><div class=\"hs-code-container neutral-50-light neutral-800-dark\" role=\"region\" aria-label=\"Code block\">\n\n<pre class=\"wp-block-code\"><code>export interface TrafficSeries {\n    name: string;\n    data: number&#091;];\n}\nexport interface TrafficData {\n    id: string;\n    year: number;\n    series: TrafficSeries&#091;];\n}<\/code><\/pre>\n\n\n<div class=\"wp-block-highsoft-hs-button\"><button type=\"button\" class=\"hc-button hc-button--white hc-button--size-200\" data-copy-code=\"true\" aria-label=\"Copy\">Copy<\/button><\/div>\n\n<\/div><\/div>\n\n\n\n<p style=\"margin-top:0\">These TypeScript interfaces describe the shape of the data we expect to receive. This lets the editor autocomplete field names and flag typos during development.<\/p>\n<\/div>\n<\/div>\n\n\n\n<div class=\"wp-block-group is-vertical is-layout-flex wp-container-core-group-is-layout-3210c5b8 wp-block-group-is-layout-flex\" style=\"margin-top:var(--wp--preset--spacing--40)\">\n<h3 class=\"wp-block-heading\" style=\"margin-bottom:var(--wp--preset--spacing--50)\">Step 6: Create the useFetch Hook<\/h3>\n\n\n\n<div class=\"wp-block-group is-vertical is-layout-flex wp-container-core-group-is-layout-accb2a1d wp-block-group-is-layout-flex\" style=\"margin-top:0;margin-bottom:0\">\n<p style=\"margin-bottom:0\">Create <code>src\/hooks\/useFetch.ts<\/code>:<\/p>\n\n\n\n<div class=\"hs-code-outer-container\"><div class=\"hs-code-container neutral-50-light neutral-800-dark\" role=\"region\" aria-label=\"Code block\">\n\n<pre class=\"wp-block-code\"><code>import { useState, useEffect } from \"react\";\ninterface UseFetchResult&lt;T&gt; {\n    data: T | null;\n    loading: boolean;\n    error: string | null;\n}\nconst SIMULATED_DELAY_MS = 1000;\nexport function useFetch&lt;T&gt;(url: string): UseFetchResult&lt;T&gt; {\n    const &#091;data, setData] = useState&lt;T | null&gt;(null);\n    const &#091;loading, setLoading] = useState&lt;boolean&gt;(true);\n    const &#091;error, setError] = useState&lt;string | null&gt;(null);\n    useEffect(() =&gt; {\n        \/\/ Intentionally do not reset data here. Instead, we keep the previous payload.\n        \/\/ This lets the chart remain visible while new data is fetched.\n        setLoading(true);\n        setError(null);\n        let cancelled = false;\n        const fetchData = async () =&gt; {\n            try {\n                const response = await fetch(url);\n                if (!response.ok) {\n                    throw new Error(`HTTP error: ${response.status}`);\n                }\n                const json: T = await response.json();\n                \/\/ Artificial delay to simulate real-world network latency.\n                \/\/ In a production app you would remove this.\n                await new Promise((resolve) =&gt;\n                    setTimeout(resolve, SIMULATED_DELAY_MS)\n                );\n                if (!cancelled) {\n                    setData(json);\n                    setLoading(false);\n                }\n            } catch (err) {\n                if (!cancelled) {\n                    setError(\n                        err instanceof Error\n                            ? err.message\n                            : \"An unknown error occurred\"\n                    );\n                    setLoading(false);\n                }\n            }\n        };\n        fetchData();\n        \/\/ Cleanup: if the component unmounts mid-fetch, discard the result.\n        return () =&gt; {\n            cancelled = true;\n        };\n    }, &#091;url]);\n    return { data, loading, error };\n}<\/code><\/pre>\n\n\n<div class=\"wp-block-highsoft-hs-button\"><button type=\"button\" class=\"hc-button hc-button--white hc-button--size-200\" data-copy-code=\"true\" aria-label=\"Copy\">Copy<\/button><\/div>\n\n<\/div><\/div>\n\n\n\n<p style=\"margin-top:0\">This hook manages the fetching logic of our app. It accepts a URL and returns three pieces of state: <code>data<\/code>, <code>loading<\/code>, and <code>error<\/code>. On the first render, <code>data<\/code> starts as <code>null<\/code> and the hook immediately begins fetching the 2025 data. Once the response arrives, <code>data<\/code> is updated and loading becomes <code>false<\/code>. On later refetches, the previous data is kept until the new response arrives.<\/p>\n\n\n\n<p>Some things to note about the <code>useFetch<\/code> hook:<\/p>\n<\/div>\n\n\n\n<ul class=\"wp-block-list has-font-size-300-font-size\">\n<li style=\"margin-top:var(--wp--preset--spacing--20);margin-bottom:var(--wp--preset--spacing--20)\">Because previous data is kept during refetching, we can keep rendering the existing chart while fetching new data. In the next step, we will use the <code>chartLoading<\/code> class to fade the current chart while waiting for the data to arrive.<\/li>\n\n\n\n<li style=\"margin-top:var(--wp--preset--spacing--20);margin-bottom:var(--wp--preset--spacing--20)\">The <code>cancelled<\/code> flag prevents an older request from updating state if the component unmounts before it completes.<\/li>\n<\/ul>\n\n\n\n<p>The artificial delay (<code>setTimeout<\/code>) is here to make the loading state visible in our demo. It is meant to mimic the loading behavior you would typically see in an SPA that fetches data from a server.<\/p>\n<\/div>\n\n\n\n<div class=\"wp-block-group is-vertical is-layout-flex wp-container-core-group-is-layout-3210c5b8 wp-block-group-is-layout-flex\" style=\"margin-top:var(--wp--preset--spacing--40)\">\n<h3 class=\"wp-block-heading\" style=\"margin-bottom:var(--wp--preset--spacing--50)\">Step 7: Create the Series Wrapper Component<\/h3>\n\n\n\n<div class=\"wp-block-group is-vertical is-layout-flex wp-container-core-group-is-layout-accb2a1d wp-block-group-is-layout-flex\" style=\"margin-top:0;margin-bottom:0\">\n<p style=\"margin-bottom:0\">Highcharts React lets you wrap its components in your own functions to keep chart code organised. The wrapper receives props from its parent (<code>TrafficChart<\/code>, built in the next step) and returns a Highcharts component. Note that no React hooks are allowed inside, because Highcharts React calls wrapper functions directly, outside of React&#8217;s rendering cycle.<\/p>\n\n\n\n<p style=\"margin-top:0;margin-bottom:0\">Create <code>src\/charts\/TrafficAreaSplineSeries.tsx<\/code>:<\/p>\n\n\n\n<div class=\"hs-code-outer-container\"><div class=\"hs-code-container neutral-50-light neutral-800-dark\" role=\"region\" aria-label=\"Code block\">\n\n<pre class=\"wp-block-code\"><code>import { Highcharts } from \"@highcharts\/react\";\nimport { AreaSplineSeries } from \"@highcharts\/react\/series\/AreaSpline\";\ninterface TrafficAreaSplineSeriesProps {\n    name: string;\n    data: number&#091;];\n    color: string;\n}\nexport function TrafficAreaSplineSeries({\n    name,\n    data,\n    color\n}: TrafficAreaSplineSeriesProps) {\n    return (\n        &lt;AreaSplineSeries\n            name={name}\n            data={data}\n            color={color}\n            options={{\n                marker: {\n                    fillColor: Highcharts.color(color).brighten(-0.1).get(),\n                    lineColor: Highcharts.color(color).brighten(0.8).get(),\n                    lineWidth: 1\n                },\n                fillColor: {\n                    linearGradient: { x1: 0, y1: 0, x2: 0, y2: 1 },\n                    stops: &#091;\n                        &#091;0, color],\n                        &#091;1, color + \"00\"]\n                    ]\n                }\n            }}\n        \/&gt;\n    );\n}<\/code><\/pre>\n\n\n<div class=\"wp-block-highsoft-hs-button\"><button type=\"button\" class=\"hc-button hc-button--white hc-button--size-200\" data-copy-code=\"true\" aria-label=\"Copy\">Copy<\/button><\/div>\n\n<\/div><\/div>\n\n\n\n<p style=\"margin-top:0\">This wrapper owns all the per-series styling: the marker colors derived from the base color via <code>Highcharts.color().brighten()<\/code>, and the vertical gradient fill. It receives <code>name<\/code>, <code>data<\/code>, and <code>color<\/code> and forwards them as props directly on <code>&lt;AreaSplineSeries&gt;<\/code> instead of nesting them inside <code>options<\/code>.<\/p>\n<\/div>\n<\/div>\n\n\n\n<div class=\"wp-block-group is-vertical is-layout-flex wp-container-core-group-is-layout-3210c5b8 wp-block-group-is-layout-flex\" style=\"margin-top:var(--wp--preset--spacing--40)\">\n<h3 class=\"wp-block-heading\" style=\"margin-bottom:var(--wp--preset--spacing--50)\">Step 8: Build the Chart Component<\/h3>\n\n\n\n<div class=\"wp-block-group is-vertical is-layout-flex wp-container-core-group-is-layout-accb2a1d wp-block-group-is-layout-flex\" style=\"margin-top:0;margin-bottom:0\">\n<p style=\"margin-bottom:0\">Create src\/charts\/TrafficChart.tsx:<\/p>\n\n\n\n<div class=\"hs-code-outer-container\"><div class=\"hs-code-container neutral-50-light neutral-800-dark\" role=\"region\" aria-label=\"Code block\">\n\n<pre class=\"wp-block-code\"><code>import {\n    Chart,\n    Title,\n    Subtitle,\n    XAxis,\n    YAxis,\n    Tooltip,\n    Legend,\n    PlotOptions\n} from \"@highcharts\/react\";\nimport type { TrafficData } from \"..\/types\";\nimport { TrafficAreaSplineSeries } from \".\/TrafficAreaSplineSeries\";\nimport { Exporting } from \"@highcharts\/react\/modules\/Exporting\";\nimport { Accessibility } from \"@highcharts\/react\/modules\/Accessibility\";\ninterface TrafficChartProps {\n    data: TrafficData;\n}\nconst colors = &#091;\"#47abc9\", \"#5064e7\", \"#7c31e6\"];\nexport default function TrafficChart({ data }: TrafficChartProps) {\n    const categories = &#091;\n        \"Jan\",\n        \"Feb\",\n        \"Mar\",\n        \"Apr\",\n        \"May\",\n        \"Jun\",\n        \"Jul\",\n        \"Aug\",\n        \"Sep\",\n        \"Oct\",\n        \"Nov\",\n        \"Dec\"\n    ];\n    return (\n        &lt;Chart\n            colors={colors}\n            containerProps={{ style: { width: \"100%\", height: \"420px\" } }}\n        &gt;\n            &lt;Title&gt;Monthly Website Traffic {data.year}&lt;\/Title&gt;\n            &lt;Subtitle&gt;Visits by channel&lt;\/Subtitle&gt;\n            &lt;XAxis categories={categories} crosshair={{}}&gt;\n                Month\n            &lt;\/XAxis&gt;\n            &lt;YAxis max={20000}&gt;Visits&lt;\/YAxis&gt;\n            &lt;Tooltip shared valueDecimals={0} valueSuffix=' visits' \/&gt;\n            &lt;Legend enabled \/&gt;\n            &lt;PlotOptions series={{ marker: { enabled: false } }} \/&gt;\n            {data.series.map((s, i) =&gt; (\n                &lt;TrafficAreaSplineSeries\n                    key={s.name}\n                    name={s.name}\n                    data={s.data}\n                    color={colors&#091;i]}\n                \/&gt;\n            ))}\n            &lt;Exporting \/&gt;\n            &lt;Accessibility \/&gt;\n        &lt;\/Chart&gt;\n    );\n}<\/code><\/pre>\n\n\n<div class=\"wp-block-highsoft-hs-button\"><button type=\"button\" class=\"hc-button hc-button--white hc-button--size-200\" data-copy-code=\"true\" aria-label=\"Copy\">Copy<\/button><\/div>\n\n<\/div><\/div>\n\n\n\n<p style=\"margin-top:0\">This is our chart component. It receives <code>TrafficData<\/code> as a prop and renders a Highcharts chart using JSX components. When new data arrives, you just pass it as props, and React automatically updates the chart. In vanilla Highcharts, you would have to call methods like <code>chart.update()<\/code> or <code>chart.series[0].setData()<\/code> to update the chart, but in Highcharts React this happens automatically.<\/p>\n\n\n\n<p style=\"margin-top:0;margin-bottom:0\">In this tutorial we are focusing on the SPA data fetching pattern, so we won&#8217;t dive into the chart configuration, but you can use the <a href=\"https:\/\/www.highcharts.com\/docs\/react\/getting-started\">Highcharts React docs<\/a> and <a href=\"https:\/\/api.highcharts.com\/highcharts\/\">Highcharts API-reference<\/a> if you want to look into the details.<\/p>\n<\/div>\n<\/div>\n\n\n\n<div class=\"wp-block-group is-vertical is-layout-flex wp-container-core-group-is-layout-3210c5b8 wp-block-group-is-layout-flex\" style=\"margin-top:var(--wp--preset--spacing--40)\">\n<h3 class=\"wp-block-heading\" style=\"margin-bottom:var(--wp--preset--spacing--50)\">Step 9: Wire It Together in App.tsx<\/h3>\n\n\n\n<div class=\"wp-block-group is-vertical is-layout-flex wp-container-core-group-is-layout-accb2a1d wp-block-group-is-layout-flex\" style=\"margin-top:0;margin-bottom:0\">\n<p style=\"margin-bottom:0\">Replace <code>src\/App.tsx<\/code> with:<\/p>\n\n\n\n<div class=\"hs-code-outer-container\"><div class=\"hs-code-container neutral-50-light neutral-800-dark\" role=\"region\" aria-label=\"Code block\">\n\n<pre class=\"wp-block-code\"><code>import { useState } from \"react\";\nimport { useFetch } from \".\/hooks\/useFetch\";\nimport TrafficChart from \".\/charts\/TrafficChart\";\nimport type { TrafficData } from \".\/types\";\nimport \".\/App.css\";\nconst YEARS = &#091;\"2025\", \"2024\", \"2023\"];\nexport default function App() {\n    const &#091;selectedYear, setSelectedYear] = useState&lt;string&gt;(\"2025\");\n    const { data, loading, error } = useFetch&lt;TrafficData&gt;(\n        `\/data\/traffic-${selectedYear}.json`\n    );\n    const hasChartData = data !== null;\n    return (\n        &lt;div className='page'&gt;\n            &lt;header className='header'&gt;\n                &lt;h1&gt;Monthly Website Traffic&lt;\/h1&gt;\n            &lt;\/header&gt;\n            &lt;div className='controls'&gt;\n                &lt;span className='label'&gt;Select year:&lt;\/span&gt;\n                {YEARS.map((year) =&gt; (\n                    &lt;button\n                        key={year}\n                        onClick={() =&gt; setSelectedYear(year)}\n                        className={`yearBtn ${selectedYear === year ? \"active\" : \"\"}`}\n                    &gt;\n                        {year}\n                    &lt;\/button&gt;\n                ))}\n            &lt;\/div&gt;\n            &lt;div className='chartWrapper'&gt;\n                {hasChartData &amp;&amp; (\n                    &lt;div\n                        className={`chartContent ${loading ? \"chartLoading\" : \"\"}`}\n                    &gt;\n                        &lt;TrafficChart data={data} \/&gt;\n                    &lt;\/div&gt;\n                )}\n                {loading &amp;&amp; (\n                    &lt;div className='loading'&gt;\n                        &lt;div className='spinner' \/&gt;\n                        &lt;p&gt;Pretending to fetch data from a server\u2026&lt;\/p&gt;\n                    &lt;\/div&gt;\n                )}\n                {error &amp;&amp; (\n                    &lt;div className='error'&gt;\n                        &lt;p&gt;\u26a0\ufe0f Could not load data: {error}&lt;\/p&gt;\n                        &lt;p className='errorHint'&gt;\n                            Make sure the Vite dev server is running and the{\" \"}\n                            &lt;code&gt;public\/data\/&lt;\/code&gt; files exist.\n                        &lt;\/p&gt;\n                    &lt;\/div&gt;\n                )}\n                {!loading &amp;&amp; !error &amp;&amp; !hasChartData &amp;&amp; (\n                    &lt;div className='emptyState'&gt;\n                        &lt;p&gt;No traffic data available.&lt;\/p&gt;\n                    &lt;\/div&gt;\n                )}\n            &lt;\/div&gt;\n        &lt;\/div&gt;\n    );\n}<\/code><\/pre>\n\n\n<div class=\"wp-block-highsoft-hs-button\"><button type=\"button\" class=\"hc-button hc-button--white hc-button--size-200\" data-copy-code=\"true\" aria-label=\"Copy\">Copy<\/button><\/div>\n\n<\/div><\/div>\n\n\n\n<p style=\"margin-top:0\">When the user clicks a year button, <code>setSelectedYear<\/code> updates the state. That causes <code>useFetch<\/code> to re-run with the new URL, which triggers a fresh fetch.<\/p>\n\n\n\n<p>A few things to notice:<\/p>\n\n\n\n<ul class=\"wp-block-list has-font-size-300-font-size\">\n<li style=\"margin-top:var(--wp--preset--spacing--20);margin-bottom:var(--wp--preset--spacing--20)\"><code>hasChartData<\/code> is true whenever there is a previous fetch result in memory. Because <code>useFetch<\/code> preserves its last data value during a refetch, <code>hasChartData<\/code> stays <code>true<\/code> while the new request is running. The previous chart remains rendered.<\/li>\n\n\n\n<li style=\"margin-top:var(--wp--preset--spacing--20);margin-bottom:var(--wp--preset--spacing--20)\">While loading, the previous chart receives the <code>chartLoading<\/code> CSS class, which blurs and dims it. This signals that the data is being refreshed without removing the chart entirely.<\/li>\n\n\n\n<li style=\"margin-top:var(--wp--preset--spacing--20);margin-bottom:var(--wp--preset--spacing--20)\">The stale chart and spinner render at the same time. The stale chart sits behind the loading spinner.<\/li>\n\n\n\n<li style=\"margin-top:var(--wp--preset--spacing--20);margin-bottom:var(--wp--preset--spacing--20)\">The empty state only appears on the very first load, before any data has arrived.<\/li>\n<\/ul>\n\n\n\n<p>This is the SPA pattern: <strong>user interaction \u2192 state change \u2192 targeted data fetch \u2192 chart update<\/strong> without a page reload.<\/p>\n<\/div>\n<\/div>\n\n\n\n<div class=\"wp-block-group is-vertical is-layout-flex wp-container-core-group-is-layout-3210c5b8 wp-block-group-is-layout-flex\" style=\"margin-top:var(--wp--preset--spacing--40)\">\n<h3 class=\"wp-block-heading\" style=\"margin-bottom:var(--wp--preset--spacing--50)\">Step 10: Run the App<\/h3>\n\n\n\n<div class=\"wp-block-group is-vertical is-layout-flex wp-container-core-group-is-layout-c35747d5 wp-block-group-is-layout-flex\">\n<p>Open your browser and navigate to <code>http:\/\/localhost:5173<\/code>. You should see the chart loaded with 2025 data.<\/p>\n\n\n\n<p>Now try clicking the year selection buttons to see the on-demand fetching pattern. When you click a different year, the chart fades and the spinner appears for about a second while data is fetched, then the chart updates with the new data.<\/p>\n<\/div>\n<\/div>\n<\/div>\n<\/div>\n\n\n\n<div class=\"wp-block-group is-vertical is-layout-flex wp-container-core-group-is-layout-355c4241 wp-block-group-is-layout-flex\">\n<h2 class=\"wp-block-heading\">Summary<\/h2>\n\n\n\n<div class=\"wp-block-group is-vertical is-layout-flex wp-container-core-group-is-layout-355c4241 wp-block-group-is-layout-flex\">\n<p>Here is what we built and what each piece does in our app:<\/p>\n\n\n\n<div class=\"wp-block-highsoft-hs-table hs-table-block hs-table--border-grid transparent\" style=\"--hs-table-align:left\"><div class=\"hs-table-block__wrapper\"><div id=\"hs-table-summary\" class=\"screen-reader-text\">App components and their roles<\/div><table class=\"hs-table-block__table\" aria-describedby=\"hs-table-summary\"><thead><tr><th id=\"hs-table-header-0\" scope=\"col\" class=\"transparent transparent\"><div class=\"hs-table-block__cell-inner\" data-layout=\"row\"><div class=\"hs-table-block__cell-content-wrapper\" data-align-items=\"flex-start\" data-justify-content=\"flex-start\"><span class=\"hs-table-block__cell-text\"><strong>Piece<\/strong><\/span><\/div><\/div><\/th><th id=\"hs-table-header-1\" scope=\"col\" class=\"transparent transparent\"><div class=\"hs-table-block__cell-inner\" data-layout=\"row\"><div class=\"hs-table-block__cell-content-wrapper\" data-align-items=\"flex-start\" data-justify-content=\"flex-start\"><span class=\"hs-table-block__cell-text\"><strong>What it does<\/strong><\/span><\/div><\/div><\/th><\/tr><\/thead><tbody><tr><td headers=\"hs-table-header-0\" class=\"transparent transparent\" colspan=\"1\" rowspan=\"1\"><div class=\"hs-table-block__cell-inner\" data-layout=\"row\"><div class=\"hs-table-block__cell-content-wrapper\" data-align-items=\"flex-start\" data-justify-content=\"flex-start\"><span class=\"hs-table-block__cell-text\">public\/data\/ JSON files<\/span><\/div><\/div><\/td><td headers=\"hs-table-header-1\" class=\"transparent transparent\" colspan=\"1\" rowspan=\"1\"><div class=\"hs-table-block__cell-inner\" data-layout=\"row\"><div class=\"hs-table-block__cell-content-wrapper\" data-align-items=\"flex-start\" data-justify-content=\"flex-start\"><span class=\"hs-table-block__cell-text\">Static JSON served by Vite.<\/span><\/div><\/div><\/td><\/tr><tr><td headers=\"hs-table-header-0\" class=\"transparent transparent\" colspan=\"1\" rowspan=\"1\"><div class=\"hs-table-block__cell-inner\" data-layout=\"row\"><div class=\"hs-table-block__cell-content-wrapper\" data-align-items=\"flex-start\" data-justify-content=\"flex-start\"><span class=\"hs-table-block__cell-text\">useFetch hook<\/span><\/div><\/div><\/td><td headers=\"hs-table-header-1\" class=\"transparent transparent\" colspan=\"1\" rowspan=\"1\"><div class=\"hs-table-block__cell-inner\" data-layout=\"row\"><div class=\"hs-table-block__cell-content-wrapper\" data-align-items=\"flex-start\" data-justify-content=\"flex-start\"><span class=\"hs-table-block__cell-text\">Manages the fetch lifecycle: loading, data, error.<\/span><\/div><\/div><\/td><\/tr><tr><td headers=\"hs-table-header-0\" class=\"transparent transparent\" colspan=\"1\" rowspan=\"1\"><div class=\"hs-table-block__cell-inner\" data-layout=\"row\"><div class=\"hs-table-block__cell-content-wrapper\" data-align-items=\"flex-start\" data-justify-content=\"flex-start\"><span class=\"hs-table-block__cell-text\">Simulated delay<\/span><\/div><\/div><\/td><td headers=\"hs-table-header-1\" class=\"transparent transparent\" colspan=\"1\" rowspan=\"1\"><div class=\"hs-table-block__cell-inner\" data-layout=\"row\"><div class=\"hs-table-block__cell-content-wrapper\" data-align-items=\"flex-start\" data-justify-content=\"flex-start\"><span class=\"hs-table-block__cell-text\">Makes the loading state visible for tutorial purposes (remove in production).<\/span><\/div><\/div><\/td><\/tr><tr><td headers=\"hs-table-header-0\" class=\"transparent transparent\" colspan=\"1\" rowspan=\"1\"><div class=\"hs-table-block__cell-inner\" data-layout=\"row\"><div class=\"hs-table-block__cell-content-wrapper\" data-align-items=\"flex-start\" data-justify-content=\"flex-start\"><span class=\"hs-table-block__cell-text\">TrafficAreaSplineSeries<\/span><\/div><\/div><\/td><td headers=\"hs-table-header-1\" class=\"transparent transparent\" colspan=\"1\" rowspan=\"1\"><div class=\"hs-table-block__cell-inner\" data-layout=\"row\"><div class=\"hs-table-block__cell-content-wrapper\" data-align-items=\"flex-start\" data-justify-content=\"flex-start\"><span class=\"hs-table-block__cell-text\">Wrapper component that owns per-series styling.<\/span><\/div><\/div><\/td><\/tr><tr><td headers=\"hs-table-header-0\" class=\"transparent transparent\" colspan=\"1\" rowspan=\"1\"><div class=\"hs-table-block__cell-inner\" data-layout=\"row\"><div class=\"hs-table-block__cell-content-wrapper\" data-align-items=\"flex-start\" data-justify-content=\"flex-start\"><span class=\"hs-table-block__cell-text\">TrafficChart<\/span><\/div><\/div><\/td><td headers=\"hs-table-header-1\" class=\"transparent transparent\" colspan=\"1\" rowspan=\"1\"><div class=\"hs-table-block__cell-inner\" data-layout=\"row\"><div class=\"hs-table-block__cell-content-wrapper\" data-align-items=\"flex-start\" data-justify-content=\"flex-start\"><span class=\"hs-table-block__cell-text\">Renders fetched data declaratively using Highcharts React.<\/span><\/div><\/div><\/td><\/tr><tr><td headers=\"hs-table-header-0\" class=\"transparent transparent\" colspan=\"1\" rowspan=\"1\"><div class=\"hs-table-block__cell-inner\" data-layout=\"row\"><div class=\"hs-table-block__cell-content-wrapper\" data-align-items=\"flex-start\" data-justify-content=\"flex-start\"><span class=\"hs-table-block__cell-text\">App<\/span><\/div><\/div><\/td><td headers=\"hs-table-header-1\" class=\"transparent transparent\" colspan=\"1\" rowspan=\"1\"><div class=\"hs-table-block__cell-inner\" data-layout=\"row\"><div class=\"hs-table-block__cell-content-wrapper\" data-align-items=\"flex-start\" data-justify-content=\"flex-start\"><span class=\"hs-table-block__cell-text\">Connects user interaction, data fetching, and chart rendering.<\/span><\/div><\/div><\/td><\/tr><\/tbody><\/table><\/div><\/div>\n\n\n\n<p>Instead of reloading the page, we fetch only the data we need and update the chart component. The server provides raw data; everything else happens in the browser.<\/p>\n\n\n\n<p><strong>Moving to production:<\/strong> Remove the simulated delay in <code>useFetch.ts<\/code>, replace the static JSON files with a real API endpoint, and add caching to avoid redundant refetches.<\/p>\n<\/div>\n<\/div>\n","protected":false},"excerpt":{"rendered":"<p>In this tutorial we will build a simple React web app that fetches data from static JSON files and uses that data to create a chart using Highcharts React. We are doing this to mimic fetching data from the server in a Single Page Application (SPA) setting. We will learn what an SPA actually is, [&hellip;]<\/p>\n","protected":false},"author":288,"featured_media":30634,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"meta_title":"Highcharts React Tutorial: On-Demand Data Fetching in a React SPA","meta_description":"A step-by-step guide to building a React SPA that fetches data on demand and renders it with @highcharts\/react v5. Covers the fetch-load-render cycle, TypeScript types, and the declarative JSX chart API.","hc_selected_options":[],"footnotes":""},"categories":[210],"tags":[1094,824,1096],"coauthors":[1110],"class_list":["post-30628","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-tutorials","tag-highcharts-core","tag-react","tag-typescript"],"_links":{"self":[{"href":"https:\/\/www.highcharts.com\/blog\/wp-json\/wp\/v2\/posts\/30628","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\/288"}],"replies":[{"embeddable":true,"href":"https:\/\/www.highcharts.com\/blog\/wp-json\/wp\/v2\/comments?post=30628"}],"version-history":[{"count":3,"href":"https:\/\/www.highcharts.com\/blog\/wp-json\/wp\/v2\/posts\/30628\/revisions"}],"predecessor-version":[{"id":30671,"href":"https:\/\/www.highcharts.com\/blog\/wp-json\/wp\/v2\/posts\/30628\/revisions\/30671"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/www.highcharts.com\/blog\/wp-json\/wp\/v2\/media\/30634"}],"wp:attachment":[{"href":"https:\/\/www.highcharts.com\/blog\/wp-json\/wp\/v2\/media?parent=30628"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.highcharts.com\/blog\/wp-json\/wp\/v2\/categories?post=30628"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.highcharts.com\/blog\/wp-json\/wp\/v2\/tags?post=30628"},{"taxonomy":"author","embeddable":true,"href":"https:\/\/www.highcharts.com\/blog\/wp-json\/wp\/v2\/coauthors?post=30628"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}