Skip to content
Modern illustration of React and HubSpot CMS integration with code editor, UI components, and developers building a dynamic web application system
King DJ24 March 20267 min read

Complete Guide: Setting Up a React HubSpot Theme (Step-by-Step with Real Project Experience)

If you want to build a dynamic, modern website on HubSpot CMS using React, this comprehensive guide covers everything. We'll walk through the setup process and share real-world debugging tips from an actual job-portal project. By following along, you'll learn both best practices and how to fix common issues that crop up in production.

🧩 Prerequisites

Before you begin, make sure you have the essentials installed:

  • Node.js (v18+; Node 20 recommended)【2†L57-L59】【8†L53-L56】.
  • HubSpot CLI (download via npm install -g @hubspot/cli)【8†L53-L56】.
  • A HubSpot developer account (a CMS sandbox is ideal for testing)【2†L54-L59】.
  • Basic React and command-line familiarity. 

Next, authenticate your HubSpot CLI with your portal. You can run:

hs accounts auth  

Follow the prompts to paste your personal access key. (HubSpot’s docs recommend using hs accounts auth over the older hs auth command for new projects【11†L117-L121】.)

🏗️ Step 1: Create Your React Theme Project

Now initialize a new HubSpot CMS React theme. The recommended approach is to use the HubSpot project boilerplate script:


npx @hubspot/create-cms-theme@latest
# Follow prompts to name your theme (e.g., "job-portal-theme")
cd job-portal-theme
npm install
npm run start
  

Running npm run start launches the local dev server (powered by hs-cms-dev-server)【16†L13-L17】. You can then preview your work at http://localhost:3000 and even proxy live HubSpot pages to test changes.

📁 Step 2: Understand the Folder Structure

After creation, your project has a structure like:


projectName/
├── hsproject.json
└── src/
    └── theme/
        ├── theme-hsmeta.json
        └── my-theme/
            ├── assets/
            ├── components/
            ├── styles/
            ├── templates/
            ├── fields.json
            ├── package.json
            └── theme.json
  

Here, components/ will hold your React modules (and "islands" for interactivity), styles/ is for CSS, and theme.json contains your theme metadata【8†L88-L97】【8†L118-L123】. This separation helps manage assets: e.g. static images go in assets/, and page templates in templates/【8†L118-L123】.

⚛️ Step 3: Create a React Module

HubSpot CMS React modules use a specific structure. Each module typically has:

  • An index.jsx that exports a named Component and fields.
  • A UI component file (e.g. MyComponent.jsx) for rendering JSX.
  • A Fields file defining HubSpot editor fields.

For example, let's create a simple Header module under components/modules/Header/.

index.jsx

import { Component as HeaderComponent } from './HeaderComponent';
import { fields } from './HeaderFields';

export { fields };

export function Component(props) {
  return <HeaderComponent {...props} />;
}
  
HeaderComponent.jsx

import styles from '../../../styles/header.module.css';

export function Component({ fieldValues }) {
  return (
    <header className={styles.header}>
      <h1>{fieldValues.title}</h1>
    </header>
  );
}
  
HeaderFields.jsx

import { ModuleFields, TextField } from '@hubspot/cms-components/fields';

export const fields = (
  <ModuleFields>
    <TextField
      name="title"
      label="Header Title"
      default="Job Portal"
    />
  </ModuleFields>
);
  

Note how we use HubSpot’s @hubspot/cms-components/fields to define editable fields. As HubSpot docs explain, React modules use the same field types as HubL modules (with added TypeScript support)【3†L43-L50】. In the example above, the field title will appear in the editor sidebar.

⚠️ Common Error: Named Component Export

If you see an error like:


Module does not have a named Component export
  

It means your module entry file isn’t exporting function Component by name. HubSpot requires a named export called Component. The fix is to ensure your code looks like:


export function Component() {
  return <div>Hello</div>;
}
  

 

🌐 Step 4: Integrate Dynamic Data (Google Sheets Example)

In our job portal project, we needed dynamic job listings. We stored data in Google Sheets and exposed it via an Apps Script API.

Apps Script Backend

const SHEET_ID = "YOUR_SHEET_ID";
const SHEET_NAME = "Jobs";

function doGet() {
  const sheet = SpreadsheetApp
    .openById(SHEET_ID)
    .getSheetByName(SHEET_NAME);
  const data = sheet.getDataRange().getValues();
  const headers = data.shift();

  const result = data.map(row => {
    let obj = {};
    headers.forEach((h, i) => obj[h] = row[i]);
    return obj;
  });

  return ContentService
    .createTextOutput(JSON.stringify(result))
    .setMimeType(ContentService.MimeType.JSON);
}
  
Frontend Fetch (React Island)

import { useEffect, useState } from 'react';

export default function JobList() {
  const [jobs, setJobs] = useState([]);

  useEffect(() => {
    fetch('YOUR_API_URL')
      .then(res => res.json())
      .then(data => setJobs(data));
  }, []);

  return (
    <div>
      {jobs.map((job, i) => (
        <div key={i}>
          <h3>{job.title}</h3>
          <p>{job.location}</p>
        </div>
      ))}
    </div>
  );
}
  

This code will render job entries fetched from your Google Sheets API. (Make sure to deploy your Apps Script as “Anyone with link” so it’s publicly accessible.)

⚠️ Common Error: API/CORS Issue

If jobs don’t appear or you see a CORS error, it often means the Apps Script wasn’t publicly deployed. To fix this, go to the Apps Script editor and:

  • Select Deploy > New deployment.
  • Choose Web app and set Who has access to Anyone.
  • Save and use the provided URL as YOUR_API_URL.

 

🔁 Step 5: Use Islands for Interactivity

HubSpot’s CMS React uses the “islands” architecture【3†L39-L47】. You can embed client-side React components (islands) within your HubL template or module. Use them for search filters, pagination, or any interactivity.

For instance, a simple job filter island might look like:


export default function Filters({ jobs }) {
  const [query, setQuery] = useState('');
  const filtered = jobs.filter(job =>
    job.title.toLowerCase().includes(query.toLowerCase())
  );

  return (
    <>
      <input placeholder="Search jobs..." onChange={e => setQuery(e.target.value)} />
      {filtered.map((job, i) => (
        <div key={i}>{job.title}</div>
      ))}
    </>
  );
}
  

When used in a template, this island receives data (like the jobs array) from server-rendered context or props, and then runs in the browser for interactivity.

⚠️ Common Error: Hydration Mismatch

If you see a warning about hydration failed or UI mismatch, it means your server-rendered markup doesn’t match the client-side markup. This often happens if you use browser-specific APIs too early. A quick fix is:


if (typeof window !== 'undefined') {
  // safe to run browser-only code
}
  

This ensures the code only runs in the browser, preventing server-client inconsistencies.

🎨 Step 6: Global CSS and Styling

We set up a base global.css under styles/ to normalize the look:


* {
  box-sizing: border-box;
}

body {
  margin: 0;
  font-family: system-ui, sans-serif;
}

h1, h2, h3 {
  margin: 0;
}
  

Using global styles prevents weird layout shifts between modules. We also create CSS module files (like header.module.css) for component-specific styles, imported into each component as shown above.

⚠️ Common Error: Nothing Rendering

Occasionally, a module’s content won’t show up in HubSpot. Common culprits include:

  • Wrong import paths: Double-check relative paths when importing CSS or components. For example: import styles from '../../../styles/header.module.css';.
  • Missing exports: Ensure you exported both the component and fields properly as shown.

 

📦 Step 7: Upload and Deploy

Once everything is ready, upload your project to HubSpot using the CLI:


hs project upload
  

This command builds and deploys your theme to your HubSpot account. During upload, HubSpot runs build health checks to catch issues early【3†L104-L109】. After a successful build, your theme will be available in the Design Manager for creating pages.

 

🚀 Real Project Progress (Job Portal Build)

Here’s how our job portal project evolved:

  • Phase 1 (Foundation): Set up the React theme and basic static modules (header, footer, job card).
  • Phase 2 (Data): Integrated Google Sheets API for dynamic job listings. Display jobs on a page.
  • Phase 3 (UX): Added search filters and pagination using client-side React islands.
  • Phase 4 (Optimization): Implemented caching (e.g., localStorage) and performance tweaks.

Throughout this process, we encountered and fixed errors like missing component exports, hydration mismatches, and import path issues. Each fix made the solution more robust and scaled to real-world needs.

🧠 Best Practices & Tips

  • Modularize: Keep components small and reusable. Separate fields logic, UI, and metadata for clarity.
  • Consistent Structure: Follow the HubSpot project structure (using components/, islands/, etc.) for maintainability【8†L118-L123】.
  • Use Islands Wisely: Only make parts of the page dynamic that need it. Static rendering on the server is faster for initial load.
  • Test Locally: Frequently run npm run start and preview pages to catch issues early (thanks to the Express + Vite dev server【5†L92-L100】).
  • Avoid Mixing Logics: Don’t put heavy data-fetching logic in JSX. Use useEffect or serverless functions for APIs.

 

💡 Final Thoughts

Building a CMS React theme on HubSpot combines the familiarity of React development with the power of HubSpot’s CMS. It offers true component reusability, server-side rendering for SEO, and smooth client-side interactivity【3†L39-L47】. Most importantly, solving real errors teaches you the platform’s quirks: from required component exports to handling hydration issues.

This guide reflects our hands-on experience. If you’re planning a similar project—be it a job portal, marketing site, or any HubSpot web app—this setup is a solid foundation. We can extend it for multi-step forms, APIs, and more, while adhering to HubSpot best practices【3†L54-L62】.

👨‍💻 Need Help or Looking to Build?

If this walkthrough was helpful and you have a HubSpot React project in mind, feel free to reach out. We’ve built full HubSpot CMS solutions (including dynamic modules, workflows, and optimized themes) and can help bring your project to life with minimal fuss.

avatar

King DJ

Building custom HubSpot CMS themes, HUBL modules, smart forms, and dynamic quote templates—optimized for speed, scalability, and CRM integration.

RELATED ARTICLES