{"id":15598,"date":"2026-06-01T19:14:09","date_gmt":"2026-06-01T16:14:09","guid":{"rendered":"https:\/\/dhtmlx.com\/blog\/?p=15598"},"modified":"2026-06-01T19:25:28","modified_gmt":"2026-06-01T16:25:28","slug":"building-ssr-friendly-project-planning-apps-remix-dhtmlx-react-gantt","status":"publish","type":"post","link":"https:\/\/dhtmlx.com\/blog\/building-ssr-friendly-project-planning-apps-remix-dhtmlx-react-gantt\/","title":{"rendered":"Building SSR-Friendly Project Planning Apps with Remix and DHTMLX React Gantt"},"content":{"rendered":"<p>When building modern React apps for project management, teams are often asked to implement advanced scheduling functionalities that remain responsive as datasets grow. To help achieve this goal, DHTMLX offers the <a href=\"https:\/\/dhtmlx.com\/docs\/products\/dhtmlxGantt-for-React\/\" rel=\"noopener\" target=\"_blank\">React Gantt component<\/a> as a powerful foundation for a scheduling UI, which is also compatible with the <a href=\"https:\/\/remix.run\/\" rel=\"noopener\" target=\"_blank\">Remix framework<\/a> that delivers data to the UI more efficiently via server-side loading. In this blog post, we&#8217;ll explore the basics of integrating our React Gantt with this full-stack JavaScript framework using the official <a href=\"https:\/\/github.com\/DHTMLX\/react-gantt-remix-starter\" rel=\"noopener\" target=\"_blank\">react-gantt-remix-starter<\/a> demo as a practical reference.<\/p>\n<h2>What Remix Brings to a React Gantt App<\/h2>\n<p>Previously, we discussed the benefits of integrating comprehensive UI components with SSR frameworks on the example of our <a href=\"https:\/\/dhtmlx.com\/blog\/why-use-dhtmlx-gantt-for-react-with-next-js-in-project-management-apps\/\" rel=\"noopener\" target=\"_blank\">React Gantt and Next.js<\/a>. Using Remix, we approach the same full-stack React space via a more explicit, React Router-based architecture. The framework leans heavily on web-standard APIs rather than abstractions, thereby contributing to more predictable data flows and routing behavior.<\/p>\n<p>This straightforward approach can be a valid option for data-intensive UIs with Gantt charts, scheduling calendars, or dashboards, where active interactions on the frontend often demand routing, persistence, and server communication.<\/p>\n<p>In our starter demo, however, we deliberately focused on the rendering flow. It emphasizes the following aspects:<\/p>\n<ul>\n<li>Route-driven data flow<\/li>\n<li>Configuration object stabilization<\/li>\n<li>Gantt UI changes<\/li>\n<li>Access to the Gantt API<\/li>\n<\/ul>\n<p>Other Remix-related concepts like loaders, actions, and data persistence are not covered in the demo. Our main objective here is to show that React Gantt matches well with the Remix rendering approach without delving into the backend architecture details.<\/p>\n<p>Starting from v9.0.12, our React Gantt component supports SSR frameworks (Next.js and Remix) without extra configuration. This means that you don\u2019t need to use any special client-only wrappers to use the component in SSR environments.<\/p>\n<h2>Integration Insights<\/h2>\n<p>Now it is time to elaborate on the key integration points of DHTMLX React Gantt with Remix, listed in the section above. <\/p>\n<ul>\n<li>Rendering the Gantt chart inside the Remix route<\/li>\n<\/ul>\n<p>In this demo, the Gantt chart is implemented inside the dedicated <strong>GanttChart<\/strong> component. Then, the chart is mounted directly inside a standard Remix route component without any extra SSR handling. You just import the component and pass data to it:<\/p>\n<div class=\"codecolorer-container javascript default\" style=\"overflow:auto;white-space:nowrap;\"><div class=\"javascript codecolorer\"><span class=\"kw5\">import<\/span> GanttChart from <span class=\"st0\">'~\/components\/Gantt\/Gantt'<\/span><span class=\"sy0\">;<\/span><br \/>\n<span class=\"kw5\">import<\/span> type <span class=\"br0\">&#123;<\/span> Route <span class=\"br0\">&#125;<\/span> from <span class=\"st0\">'.\/+types\/home'<\/span><span class=\"sy0\">;<\/span><br \/>\n<span class=\"kw5\">import<\/span> <span class=\"br0\">&#123;<\/span> tasks<span class=\"sy0\">,<\/span> links <span class=\"br0\">&#125;<\/span> from <span class=\"st0\">'~\/data\/demoData'<\/span><span class=\"sy0\">;<\/span><br \/>\n<br \/>\n<span class=\"kw5\">export<\/span> <span class=\"kw1\">default<\/span> <span class=\"kw1\">function<\/span> Home<span class=\"br0\">&#40;<\/span><span class=\"br0\">&#41;<\/span> <span class=\"br0\">&#123;<\/span><br \/>\n&nbsp; <span class=\"kw1\">return<\/span> <span class=\"br0\">&#40;<\/span><br \/>\n&nbsp; &nbsp; <span class=\"sy0\">&lt;<\/span>div style<span class=\"sy0\">=<\/span><span class=\"br0\">&#123;<\/span><span class=\"br0\">&#123;<\/span> width<span class=\"sy0\">:<\/span> <span class=\"st0\">'100vw'<\/span><span class=\"sy0\">,<\/span> height<span class=\"sy0\">:<\/span> <span class=\"st0\">'100vh'<\/span> <span class=\"br0\">&#125;<\/span><span class=\"br0\">&#125;<\/span><span class=\"sy0\">&gt;<\/span><br \/>\n&nbsp; &nbsp; &nbsp; <span class=\"sy0\">&lt;<\/span>GanttChart tasks<span class=\"sy0\">=<\/span><span class=\"br0\">&#123;<\/span>tasks<span class=\"br0\">&#125;<\/span> links<span class=\"sy0\">=<\/span><span class=\"br0\">&#123;<\/span>links<span class=\"br0\">&#125;<\/span> <span class=\"sy0\">\/&gt;<\/span><br \/>\n&nbsp; &nbsp; <span class=\"sy0\">&lt;\/<\/span>div<span class=\"sy0\">&gt;<\/span><br \/>\n&nbsp; <span class=\"br0\">&#41;<\/span><span class=\"sy0\">;<\/span><br \/>\n<span class=\"br0\">&#125;<\/span><\/div><\/div>\n<p>The route is responsible for the app structure and data flow, while the Gantt component primarily focuses on rendering and user interactions with the UI. Tasks and links are passed into the chart as props, while UI edits are emitted via the <strong>save<\/strong> function discussed below.<\/p>\n<p>The demo uses a local dataset, but the same setup can later be connected to server-loaded data using special Remix loaders.<\/p>\n<p>The chart also requires a parent container with the specified height (<strong>width: &#8216;100vw&#8217;, height: &#8216;100vh&#8217;<\/strong>). Otherwise, the Gantt chart may be displayed incorrectly even with the proper component mounting inside the route.<\/p>\n<ul>\n<li>Stabilizing the Gantt configuration<\/li>\n<\/ul>\n<p>The <strong>GanttConfig<\/strong> object declares how the Gantt chart is structured and displayed through configuration options for dates, scale, controls, etc. If this object is recreated on every render cycle, the Gantt will reinitialize, which is absolutely unnecessary. That is why we wrapped the config in the React hook called <strong>useMemo<\/strong>:<\/p>\n<div class=\"codecolorer-container javascript default\" style=\"overflow:auto;white-space:nowrap;\"><div class=\"javascript codecolorer\"><span class=\"kw1\">const<\/span> config<span class=\"sy0\">:<\/span> GanttConfig <span class=\"sy0\">=<\/span> useMemo<span class=\"br0\">&#40;<\/span><br \/>\n&nbsp; <span class=\"br0\">&#40;<\/span><span class=\"br0\">&#41;<\/span> <span class=\"sy0\">=&gt;<\/span> <span class=\"br0\">&#40;<\/span><span class=\"br0\">&#123;<\/span><br \/>\n&nbsp; &nbsp; grid_width<span class=\"sy0\">:<\/span> <span class=\"nu0\">500<\/span><span class=\"sy0\">,<\/span><br \/>\n&nbsp; &nbsp; scale_height<span class=\"sy0\">:<\/span> <span class=\"nu0\">90<\/span><span class=\"sy0\">,<\/span><br \/>\n&nbsp; &nbsp; scales<span class=\"sy0\">:<\/span> <span class=\"br0\">&#91;<\/span><br \/>\n&nbsp; &nbsp; &nbsp; <span class=\"br0\">&#123;<\/span> unit<span class=\"sy0\">:<\/span> <span class=\"st0\">'year'<\/span><span class=\"sy0\">,<\/span> step<span class=\"sy0\">:<\/span> <span class=\"nu0\">1<\/span><span class=\"sy0\">,<\/span> date<span class=\"sy0\">:<\/span> <span class=\"st0\">'%Y'<\/span> <span class=\"br0\">&#125;<\/span><span class=\"sy0\">,<\/span><br \/>\n&nbsp; &nbsp; &nbsp; <span class=\"br0\">&#123;<\/span> unit<span class=\"sy0\">:<\/span> <span class=\"st0\">'month'<\/span><span class=\"sy0\">,<\/span> step<span class=\"sy0\">:<\/span> <span class=\"nu0\">1<\/span><span class=\"sy0\">,<\/span> date<span class=\"sy0\">:<\/span> <span class=\"st0\">'%M'<\/span> <span class=\"br0\">&#125;<\/span><span class=\"sy0\">,<\/span><br \/>\n&nbsp; &nbsp; &nbsp; <span class=\"br0\">&#123;<\/span> unit<span class=\"sy0\">:<\/span> <span class=\"st0\">'day'<\/span><span class=\"sy0\">,<\/span> step<span class=\"sy0\">:<\/span> <span class=\"nu0\">1<\/span><span class=\"sy0\">,<\/span> date<span class=\"sy0\">:<\/span> <span class=\"st0\">'%d %M'<\/span> <span class=\"br0\">&#125;<\/span><span class=\"sy0\">,<\/span><br \/>\n&nbsp; &nbsp; <span class=\"br0\">&#93;<\/span><span class=\"sy0\">,<\/span><br \/>\n&nbsp; <span class=\"br0\">&#125;<\/span><span class=\"br0\">&#41;<\/span><span class=\"sy0\">,<\/span><br \/>\n&nbsp; <span class=\"br0\">&#91;<\/span><span class=\"br0\">&#93;<\/span><br \/>\n<span class=\"br0\">&#41;<\/span><span class=\"sy0\">;<\/span><\/div><\/div>\n<p>By applying this memoization approach, we ensure that the Gantt configuration is created once and reused during the lifetime of the component.<\/p>\n<ul>\n<li>Enabling updates processing via the data.save callback<\/li>\n<\/ul>\n<p>When end-users interact with the Gantt chart, or more precisely, edit tasks or dependency links, these changes are reported via the <strong>data.save<\/strong> callback:<\/p>\n<div class=\"codecolorer-container javascript default\" style=\"overflow:auto;white-space:nowrap;\"><div class=\"javascript codecolorer\">data<span class=\"sy0\">=<\/span><span class=\"br0\">&#123;<\/span><span class=\"br0\">&#123;<\/span><br \/>\n&nbsp; save<span class=\"sy0\">:<\/span> <span class=\"br0\">&#40;<\/span>entity<span class=\"sy0\">:<\/span> string<span class=\"sy0\">,<\/span> action<span class=\"sy0\">:<\/span> string<span class=\"sy0\">,<\/span> data<span class=\"sy0\">:<\/span> Task <span class=\"sy0\">|<\/span> Link<span class=\"sy0\">,<\/span> id<span class=\"sy0\">:<\/span> string <span class=\"sy0\">|<\/span> number<span class=\"br0\">&#41;<\/span> <span class=\"sy0\">=&gt;<\/span> <span class=\"br0\">&#123;<\/span><br \/>\n&nbsp; &nbsp; console.<span class=\"me1\">log<\/span><span class=\"br0\">&#40;<\/span>`$<span class=\"br0\">&#123;<\/span>entity<span class=\"br0\">&#125;<\/span> <span class=\"sy0\">-<\/span> $<span class=\"br0\">&#123;<\/span>action<span class=\"br0\">&#125;<\/span> <span class=\"sy0\">-<\/span> $<span class=\"br0\">&#123;<\/span>id<span class=\"br0\">&#125;<\/span>`<span class=\"sy0\">,<\/span> data<span class=\"br0\">&#41;<\/span><span class=\"sy0\">;<\/span><br \/>\n&nbsp; <span class=\"br0\">&#125;<\/span><span class=\"sy0\">,<\/span><br \/>\n<span class=\"br0\">&#125;<\/span><span class=\"br0\">&#125;<\/span><\/div><\/div>\n<p>In our demo project, <strong>data.save<\/strong> only logs the changes. For real-world scenarios, this is exactly where you would call a Remix action function or bind these changes to the React state. Learn more about <a href=\"https:\/\/docs.dhtmlx.com\/gantt\/integrations\/react\/overview\/#bindingdata\" rel=\"noopener\" target=\"_blank\">data binding options<\/a> in the documentation.<\/p>\n<ul>\n<li>Ensuring access to the underlying Gantt API<\/li>\n<\/ul>\n<p>Finally, we would like to mention that our demo includes a reference to the Gantt instance with the useRef hook:<\/p>\n<div class=\"codecolorer-container javascript default\" style=\"overflow:auto;white-space:nowrap;\"><div class=\"javascript codecolorer\"><span class=\"kw1\">const<\/span> ganttRef <span class=\"sy0\">=<\/span> useRef<span class=\"sy0\">&lt;<\/span>ReactGanttRef<span class=\"sy0\">&gt;<\/span><span class=\"br0\">&#40;<\/span><span class=\"kw2\">null<\/span><span class=\"br0\">&#41;<\/span><span class=\"sy0\">;<\/span> <br \/>\n<br \/>\n<span class=\"kw1\">const<\/span> gantt <span class=\"sy0\">=<\/span> ganttRef.<span class=\"me1\">current<\/span><span class=\"sy0\">?<\/span>.<span class=\"me1\">instance<\/span><span class=\"sy0\">;<\/span><br \/>\n<span class=\"kw1\">if<\/span> <span class=\"br0\">&#40;<\/span><span class=\"sy0\">!<\/span>gantt<span class=\"br0\">&#41;<\/span> <span class=\"kw1\">return<\/span><span class=\"sy0\">;<\/span><br \/>\n<br \/>\n<span class=\"co1\">\/\/ here you can call ANY Gantt API method<\/span><br \/>\nconsole.<span class=\"me1\">log<\/span><span class=\"br0\">&#40;<\/span><span class=\"st0\">'All tasks:'<\/span><span class=\"sy0\">,<\/span> gantt.<span class=\"me1\">getTaskByTime<\/span><span class=\"br0\">&#40;<\/span><span class=\"br0\">&#41;<\/span><span class=\"br0\">&#41;<\/span><span class=\"sy0\">;<\/span><br \/>\ngantt.<span class=\"me1\">showDate<\/span><span class=\"br0\">&#40;<\/span><span class=\"kw1\">new<\/span> <span class=\"kw4\">Date<\/span><span class=\"br0\">&#40;<\/span><span class=\"br0\">&#41;<\/span><span class=\"br0\">&#41;<\/span><span class=\"sy0\">;<\/span><\/div><\/div>\n<p>Thus, you get direct access to the underlying Gantt API for imperative chart operations without triggering re-renders. This option may not be needed for basic setups, but it will certainly simplify future API-driven customizations when needed.<\/p>\n<p>With a clear understanding of these points, you will hardly have any doubts or confusion when working with the <a href=\"https:\/\/github.com\/DHTMLX\/react-gantt-remix-starter\" rel=\"noopener\" target=\"_blank\">react-gantt-remix-starter<\/a> repository and <a href=\"https:\/\/docs.dhtmlx.com\/gantt\/integrations\/react\/remix\/\" rel=\"noopener\" target=\"_blank\">integration guide<\/a>. <\/p>\n<h2>Closing Thoughts<\/h2>\n<p>The Remix starter demo is another building block at your disposal for creating modern project management apps with DHTMLX React Gantt. It covers the basics of bridging the gap between the frontend, based on advanced UI components, and a full-stack architecture. Following the demo approach and the associated documentation guide, you can create a foundational template that is extensible with specific persistence and state management strategies required for a given project.<\/p>\n<h2>Related Materials<\/h2>\n<ul>\n<li><a href=\"https:\/\/docs.dhtmlx.com\/gantt\/integrations\/react\/\" rel=\"noopener\" target=\"_blank\">DHTMLX React Gantt documentation<\/a><\/li>\n<li><a href=\"https:\/\/dhtmlx.com\/docs\/products\/dhtmlxGantt-for-React\/download.shtml\" rel=\"noopener\" target=\"_blank\">Free 30-day trial version of React Gantt<\/a><\/li>\n<li><a href=\"https:\/\/dhtmlx.com\/blog\/tutorials\/?tag=gantt\" rel=\"noopener\" target=\"_blank\">Other DHTMLX Gantt tutorials<\/a><\/li>\n<\/ul>\n","protected":false},"excerpt":{"rendered":"<p>When building modern React apps for project management, teams are often asked to implement advanced scheduling functionalities that remain responsive as datasets grow. To help achieve this goal, DHTMLX &#8230;<\/p>\n","protected":false},"author":101014,"featured_media":15599,"comment_status":"closed","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[71],"tags":[33,20,56],"class_list":["post-15598","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-tutorials","tag-all-posts","tag-gantt","tag-react"],"_links":{"self":[{"href":"https:\/\/dhtmlx.com\/blog\/wp-json\/wp\/v2\/posts\/15598","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/dhtmlx.com\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/dhtmlx.com\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/dhtmlx.com\/blog\/wp-json\/wp\/v2\/users\/101014"}],"replies":[{"embeddable":true,"href":"https:\/\/dhtmlx.com\/blog\/wp-json\/wp\/v2\/comments?post=15598"}],"version-history":[{"count":2,"href":"https:\/\/dhtmlx.com\/blog\/wp-json\/wp\/v2\/posts\/15598\/revisions"}],"predecessor-version":[{"id":15601,"href":"https:\/\/dhtmlx.com\/blog\/wp-json\/wp\/v2\/posts\/15598\/revisions\/15601"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/dhtmlx.com\/blog\/wp-json\/wp\/v2\/media\/15599"}],"wp:attachment":[{"href":"https:\/\/dhtmlx.com\/blog\/wp-json\/wp\/v2\/media?parent=15598"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/dhtmlx.com\/blog\/wp-json\/wp\/v2\/categories?post=15598"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/dhtmlx.com\/blog\/wp-json\/wp\/v2\/tags?post=15598"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}