---
title: Building a clean HubSpot React Header & Footer — what we did and why it works
description: Master HubSpot React development. Learn the correct folder structure to fix Vite build errors and build scalable, production-ready Header and Footer modules.
image: https://www.hubdev.in/hubfs/Profile/Images/hubspot-react-header-footer-guide.png
---

[Skip to content](https://www.hubdev.in/references/hubspot-react-header-footer-guide#main-content)

Close

- [Home](https://www.hubdev.in)
- [Projects](https://www.hubdev.in/projects)
- [Reviews](https://www.hubdev.in/reviews)
- Tools 
    - [Resume Builder](https://www.hubdev.in/modern-responsive-resume-builder)
    - [Logo Maker](https://www.hubdev.in/logo-creator)
    - [Compress Images](https://www.hubdev.in/tools/compress-image)
    - [HTML Cleaner](https://www.hubdev.in/tools/html-cleaner)
    - [Interest Calculator](https://www.hubdev.in/interest-calculator)
    - [SIP Calculator](https://www.hubdev.in/sip-calculator)
    - [Spin the Wheel](https://www.hubdev.in/spin-to-win)
    - [Email Signature Generator](https://www.hubdev.in/tools/email-signature-generator)
    - [Image to Text Extractor](https://www.hubdev.in/image-text-extractor)
    - [Text Converter](https://www.hubdev.in/text-transformer)
- [Say Hi](https://www.hubdev.in/contact)

SEARCH

[Read All Articles](https://www.hubdev.in/references)

[![logo](https://www.hubdev.in/hs-fs/hubfs/Profile/hub-dev-logo.png?width=400&height=107&name=hub-dev-logo.png "logo")](https://www.hubdev.in)[![hd-hubdev-gold](https://www.hubdev.in/hs-fs/hubfs/hd-hubdev-gold.png?width=222&height=70&name=hd-hubdev-gold.png)](https://www.hubdev.in)

- [Home](https://www.hubdev.in)
- [Projects](https://www.hubdev.in/projects)
- [Reviews](https://www.hubdev.in/reviews)
- Tools 
    - [Resume Builder](https://www.hubdev.in/modern-responsive-resume-builder)
    - [Logo Maker](https://www.hubdev.in/logo-creator)
    - [Compress Images](https://www.hubdev.in/tools/compress-image)
    - [HTML Cleaner](https://www.hubdev.in/tools/html-cleaner)
    - [Interest Calculator](https://www.hubdev.in/interest-calculator)
    - [SIP Calculator](https://www.hubdev.in/sip-calculator)
    - [Spin the Wheel](https://www.hubdev.in/spin-to-win)
    - [Email Signature Generator](https://www.hubdev.in/tools/email-signature-generator)
    - [Image to Text Extractor](https://www.hubdev.in/image-text-extractor)
    - [Text Converter](https://www.hubdev.in/text-transformer)
- [Say Hi](https://www.hubdev.in/contact)

[Read All Articles](https://www.hubdev.in/references)

Quick results for "{search\_term}"

![A vibrant, colorful featured banner image with rich gradients, modern abstract shapes, and soft lighting. Clean composition with smooth curves, subtle depth, and a visually appealing layout.](https://www.hubdev.in/hs-fs/hubfs/Profile/Images/hubspot-react-header-footer-guide.png?width=300&name=hubspot-react-header-footer-guide.png)

[King DJ](https://www.hubdev.in/references/author/hubdev)24 November 20254 min read

# Building a clean HubSpot React Header & Footer — what we did and why it works

[Previous](https://www.hubdev.in/references/top-hubspot-cms-developer-for-themes-modules-website-migrations-advanced-frontend)

<https://www.hubdev.in/references>

[Next](https://www.hubdev.in/references/state-driven-step-form-architecture)

This guide details the architecture for creating high-performance **HubSpot React modules**. We will cover how to structure your directory for the **HubSpot CMS**, resolve common **Vite build errors**, and implement best practices for exposing fields to the content editor.

If you are migrating from legacy templates or building a fresh theme, following this "Module-as-Directory" pattern is essential for passing HubSpot's production build checks.

### The Goal

Our objective is to ship well-structured, maintainable React modules that:

- Adhere to the **Module-as-Directory** architecture required by the HubSpot CLI.
- Expose **HubSpot-authorable fields** (images, text, menus) via the Design Manager.
- Render correctly inside standard **HubL templates**.
- Include a fallback menu and a dynamic **Language Switcher** using `pageContext`.
- Ship with theme-safe CSS and resolve common `EISDIR` build errors.

### The Module Directory Tree

The HubSpot React runtime (powered by Vite) expects modules to be exported from a specific folder structure. Below is the exact file tree required to prevent build errors like "Failed to load URL".

```
components/
  modules/
    Header/
      ├── index.jsx            // Entry point (Exports Component, fields, meta)
      ├── HeaderComponent.jsx  // Main UI Logic
      ├── HeaderFields.jsx     // Editor Fields Configuration
      ├── HeaderMeta.js        // Module metadata (Label, Icon, Boolean options)
      ├── Menu.jsx             // Sub-component for Navigation
      ├── MenuFields.jsx       // Sub-fields for Navigation
      ├── LanguageSwitcher.jsx // Logic for multi-language
      └── style/
          └── header.css       // Scoped CSS (Must match module directory name)
```

### Key Module Files Explained

##### 1. The Entry Point (`index.jsx`)

This file is mandatory. It aggregates your exports so HubSpot knows where to find the component logic, the field definition, and the metadata.

```
// index.jsx
import Component from "./HeaderComponent";
import fields from "./HeaderFields";
import meta from "./HeaderMeta";

export { Component, fields, meta };
```

##### 2. Field Configuration (`HeaderFields.jsx`)

This file defines what content editors see in the sidebar. We import standard fields from the **@hubspot/cms-components** library.

```
// HeaderFields.jsx
import { ModuleFields, TextField, ImageField } from "@hubspot/cms-components/fields";
import MenuFields from "./MenuFields";
import logo from "../../../assets/sprocket.svg";

const fields = (
  <ModuleFields>
    <ImageField 
      name="logo" 
      label="Logo" 
      default= 
      resizable 
    />

    <TextField 
      name="headerTitle" 
      label="Header Title" 
      default="React Header" 
    />

    {/* We import the menu fields from a separate file to keep this clean */}
    {MenuFields}

    {/* Note: We do NOT use a field for languages. That comes from pageContext. */}
  </ModuleFields>
);

export default fields;
```

##### 3. The UI Component (`HeaderComponent.jsx`)

This is where the React magic happens. Note that we retrieve **languages** from `pageContext`, not from the module fields.

```
// HeaderComponent.jsx
import "./style/header.css";
import Menu from "./Menu";
import LanguageSwitcher from "./LanguageSwitcher";

export default function HeaderComponent({ fieldValues, pageContext }) {
  const { logo, headerTitle, navigation } = fieldValues;
  
  // Robust check for languages provided by the CMS runtime
  const languages = pageContext?.languages || [];

  return (
    <header className="header-module">
      <div className="header-inner">
        <img 
          src={logo.src} 
          alt={logo.alt} 
          width={logo.width} 
          height={logo.height} 
          className="header-logo" 
        />

        <h1 className="header-title">{headerTitle}</h1>

        <Menu items={navigation} />

        <LanguageSwitcher languages={languages} />
      </div>
    </header>
  );
}
```

##### 4. Navigation with Fallback (`Menu.jsx`)

To improve the Developer Experience (DX), we add a fallback. This ensures the header looks good in local development even before a user selects a real menu in the CMS.

```
// Menu.jsx
const sampleMenu = [
  { label: "Home", url: "/" },
  { label: "About Us", url: "/about" },
  { label: "Services", url: "/services" }
];

export default function Menu({ items }) {
  // If the user hasn't selected a menu yet, use the sample
  const finalItems = Array.isArray(items) && items.length > 0 ? items : sampleMenu;

  return (
    <nav className="header-nav">
      <ul>
        {finalItems.map((item, idx) => (
          <li key={idx}><a href={item.url}>{item.label}</a></li>
        ))}
      </ul>
    </nav>
  );
}
```

### CSS and the HubSpot Style Folder Rule

HubSpot React modules have a strict convention for CSS auto-loading. You cannot simply import a CSS file from anywhere.

- **Rule 1:** Create a `style/` folder inside your module directory.
- **Rule 2:** Name the CSS file exactly the same as your module directory (e.g., `style/header.css` for a "Header" module).
- **Rule 3:** Import it at the top of your component file.

```
/* components/modules/Header/style/header.css */
.header-module { padding: 20px; background: #fff; }
.header-inner { display: flex; align-items:center; justify-content:space-between; }
.header-nav ul { display:flex; gap: 20px; list-style:none; }
```

### Troubleshooting: Common Build Errors

If your local dev server or the `hs project upload` command fails, check these common issues.

| Error Message | Root Cause | The Fix |
| --- | --- | --- |
| **EISDIR: illegal operation on a directory** | Your HubL template points to a parent folder (e.g., `modules/global`) instead of the specific module folder. | Update the path in your HubL to point explicitly to the module folder: `path="../components/modules/Header"`. |
| **Failed to load url … resolved id: header.jsx** | Vite is confused because an old file named `header.jsx` exists next to your new `Header/` folder. | Delete the legacy single-file module so the folder-based module is the only source of truth. |
| **Element type is invalid** | You are trying to import a field component that doesn't exist (e.g., `LanguageSelectorField`). | Remove invalid imports. Use `pageContext` for languages. Only import documented fields from `@hubspot/cms-components/fields`. |
| **LinkField invalid default** | The build system rejects `LinkField` without a valid default value. | Explicitly set `default={null}` on all optional LinkFields. |

### How to Call the React Module from HubL

Once your folder structure is correct, you can reference the module in your `base.hubl.html` or any drag-and-drop area. We use the `raw` tag to ensure HubSpot doesn't try to execute this example code immediately.

**Standard Module Call:**

```
{% module "header" path="../../components/modules/Header" %}
```

**Editable Widget Block:**

```
{% widget_block "header_block" type="react_component" path="../../components/modules/Header" %}
  {# Child content can go here #}
{% end_widget_block %}
```

### Critical: Footer & LinkFields

When building a Footer with social media icons, you will use the `LinkField`. The HubSpot cloud builder is strict about defaults. If a link is optional, you **must** set the default to null, otherwise the upload will fail.

```
<LinkField name="facebook" label="Facebook URL" default={null} />
```

### Final Checklist

Before you commit your changes, verify the following:

- Module lives in `components/modules/YourModuleName`.
- `index.jsx` exports `{ Component, fields, meta }`.
- Styles are located at `components/modules/YourModuleName/style/yourmodulename.css`.
- No duplicate/legacy files exist in the parent directory.
- All optional `LinkField` props are set to `default={null}`.
- Language switcher logic relies on `pageContext.languages`.

![avatar](https://www.hubdev.in/hs-fs/hubfs/Profile/dj-author-circle2-1.png?width=290&name=dj-author-circle2-1.png)

#### [King DJ](https://www.hubdev.in/references/author/hubdev)

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

<https://www.hubdev.in><https://www.facebook.com/224982578350117><https://www.linkedin.com/company/hubdev-journal><https://twitter.com/HubdevHubspot>

## RELATED ARTICLES

[![hubdevfooterLogo](https://8785843.fs1.hubspotusercontent-na1.net/hub/8785843/hubfs/hubdevfooterLogo.png?width=201&name=hubdevfooterLogo.png)](https://www.hubdev.in)

#### HubSpot CMS Developer · Front-End Engineer

Building custom HubSpot websites, modules, CRM-driven experiences, and front-end solutions with 12+ years of development experience.

- [![India](https://www.hubdev.in/hubfs/Profile/india.png)](https://www.hubdev.in/contact)
- [Contact Developer](https://www.hubdev.in/contact)

HubSpot React / Custom Object / HubDB / JavaScript / HUBL / HTML / CSS / Jinja

- [Terms](https://www.hubdev.in/terms-of-service)
- [PayPal](https://www.paypal.com/paypalme/DeepakJha999)
- [Codepen](https://codepen.io/Deets_HubDev)
- [Optimization](https://www.hubdev.in/page-speed-results)
- [My Resume](https://www.hubdev.in/developer-resume)

```json
{
  "@context" : "https://schema.org",
  "@type" : "Organization",
  "address" : {
    "@type" : "PostalAddress",
    "addressCountry" : "India",
    "addressLocality" : "DBG",
    "addressRegion" : "BH",
    "postalCode" : "",
    "streetAddress" : "LHS"
  },
  "knowsLanguage" : "en",
  "logo" : {
    "@type" : "ImageObject",
    "url" : "https://hubdev.in/hubfs/Profile/hub-dev-logo.png"
  },
  "name" : "HubDev",
  "url" : "https://www.hubdev.in/references/hubspot-react-header-footer-guide"
}
```

```json
{
  "@context" : "https://schema.org",
  "@type" : "BlogPosting",
  "abstract" : "Master HubSpot React development. Learn the correct folder structure to fix Vite build errors and build scalable, production-ready Header and Footer modules.",
  "author" : {
    "@type" : "Person",
    "name" : "King DJ"
  },
  "dateModified" : "November 23, 2025 8 PM",
  "datePublished" : "November 23, 2025 8 PM",
  "headline" : "Building a clean HubSpot React Header &amp; Footer — what we did and why it works",
  "image" : "https://8785843.fs1.hubspotusercontent-na1.net/hubfs/8785843/Profile/Images/hubspot-react-header-footer-guide.png",
  "inLanguage" : "en",
  "keywords" : "[Front-End Development, React Modules]",
  "mainEntityOfPage" : {
    "@type" : "WebPage"
  },
  "name" : "Building a clean HubSpot React Header &amp; Footer — what we did and why it works",
  "publisher" : {
    "@type" : "Organization",
    "logo" : {
      "@type" : "ImageObject",
      "url" : "https://hubdev.in/hubfs/Profile/hub-dev-logo.png"
    },
    "name" : "HubDev"
  },
  "text" : "This guide details the architecture for creating high-performance HubSpot React modules. We will cover how to structure your directory for the HubSpot CMS, resolve common Vite build errors, and implement best practices for exposing fields to the content editor. If you are migrating from legacy templates or building a fresh theme, following this \"Module-as-Directory\" pattern is essential for passing HubSpot's production build checks. The Goal Our objective is to ship well-structured, maintainable React modules that: Adhere to the Module-as-Directory architecture required by the HubSpot CLI. Expose HubSpot-authorable fields (images, text, menus) via the Design Manager. Render correctly inside standard HubL templates. Include a fallback menu and a dynamic Language Switcher using pageContext. Ship with theme-safe CSS and resolve common EISDIR build errors. The Module Directory Tree The HubSpot React runtime (powered by Vite) expects modules to be exported from a specific folder structure. Below is the exact file tree required to prevent build errors like \"Failed to load URL\". components/ modules/ Header/ ├── index.jsx // Entry point (Exports Component, fields, meta) ├── HeaderComponent.jsx // Main UI Logic ├── HeaderFields.jsx // Editor Fields Configuration ├── HeaderMeta.js // Module metadata (Label, Icon, Boolean options) ├── Menu.jsx // Sub-component for Navigation ├── MenuFields.jsx // Sub-fields for Navigation ├── LanguageSwitcher.jsx // Logic for multi-language └── style/ └── header.css // Scoped CSS (Must match module directory name) Key Module Files Explained 1. The Entry Point (index.jsx) This file is mandatory. It aggregates your exports so HubSpot knows where to find the component logic, the field definition, and the metadata. // index.jsx import Component from \"./HeaderComponent\"; import fields from \"./HeaderFields\"; import meta from \"./HeaderMeta\"; export { Component, fields, meta }; 2. Field Configuration (HeaderFields.jsx) This file defines what content editors see in the sidebar. We import standard fields from the @hubspot/cms-components library. // HeaderFields.jsx import { ModuleFields, TextField, ImageField } from \"@hubspot/cms-components/fields\"; import MenuFields from \"./MenuFields\"; import logo from \"../../../assets/sprocket.svg\"; const fields = ( {/* We import the menu fields from a separate file to keep this clean */} {MenuFields} {/* Note: We do NOT use a field for languages. That comes from pageContext. */} ); export default fields; 3. The UI Component (HeaderComponent.jsx) This is where the React magic happens. Note that we retrieve languages from pageContext, not from the module fields. // HeaderComponent.jsx import \"./style/header.css\"; import Menu from \"./Menu\"; import LanguageSwitcher from \"./LanguageSwitcher\"; export default function HeaderComponent({ fieldValues, pageContext }) { const { logo, headerTitle, navigation } = fieldValues; // Robust check for languages provided by the CMS runtime const languages = pageContext?.languages || []; return ( {headerTitle} ); } 4. Navigation with Fallback (Menu.jsx) To improve the Developer Experience (DX), we add a fallback. This ensures the header looks good in local development even before a user selects a real menu in the CMS. // Menu.jsx const sampleMenu = [ { label: \"Home\", url: \"/\" }, { label: \"About Us\", url: \"/about\" }, { label: \"Services\", url: \"/services\" } ]; export default function Menu({ items }) { // If the user hasn't selected a menu yet, use the sample const finalItems = Array.isArray(items) &amp;&amp; items.length &gt; 0 ? items : sampleMenu; return ( {finalItems.map((item, idx) =&gt; ( {item.label} ))} ); } CSS and the HubSpot Style Folder Rule HubSpot React modules have a strict convention for CSS auto-loading. You cannot simply import a CSS file from anywhere. Rule 1: Create a style/ folder inside your module directory. Rule 2: Name the CSS file exactly the same as your module directory (e.g., style/header.css for a \"Header\" module). Rule 3: Import it at the top of your component file. /* components/modules/Header/style/header.css */ .header-module { padding: 20px; background: #fff; } .header-inner { display: flex; align-items:center; justify-content:space-between; } .header-nav ul { display:flex; gap: 20px; list-style:none; } Troubleshooting: Common Build Errors If your local dev server or the hs project upload command fails, check these common issues. Error Message Root Cause The Fix EISDIR: illegal operation on a directory Your HubL template points to a parent folder (e.g., modules/global) instead of the specific module folder. Update the path in your HubL to point explicitly to the module folder: path=\"../components/modules/Header\". Failed to load url … resolved id: header.jsx Vite is confused because an old file named header.jsx exists next to your new Header/ folder. Delete the legacy single-file module so the folder-based module is the only source of truth. Element type is invalid You are trying to import a field component that doesn't exist (e.g., LanguageSelectorField). Remove invalid imports. Use pageContext for languages. Only import documented fields from @hubspot/cms-components/fields. LinkField invalid default The build system rejects LinkField without a valid default value. Explicitly set default={null} on all optional LinkFields. How to Call the React Module from HubL Once your folder structure is correct, you can reference the module in your base.hubl.html or any drag-and-drop area. We use the raw tag to ensure HubSpot doesn't try to execute this example code immediately. Standard Module Call: {% module \"header\" path=\"../../components/modules/Header\" %}  Editable Widget Block: {% widget_block \"header_block\" type=\"react_component\" path=\"../../components/modules/Header\" %} {# Child content can go here #} {% end_widget_block %}  Critical: Footer &amp; LinkFields When building a Footer with social media icons, you will use the LinkField. The HubSpot cloud builder is strict about defaults. If a link is optional, you must set the default to null, otherwise the upload will fail. Final Checklist Before you commit your changes, verify the following: Module lives in components/modules/YourModuleName. index.jsx exports { Component, fields, meta }. Styles are located at components/modules/YourModuleName/style/yourmodulename.css. No duplicate/legacy files exist in the parent directory. All optional LinkField props are set to default={null}. Language switcher logic relies on pageContext.languages.",
  "url" : "https://www.hubdev.in/references/hubspot-react-header-footer-guide",
  "wordCount" : "2032"
}
```

```json
{
  "@context" : "https://schema.org",
  "@type" : "BlogPosting",
  "author" : {
    "@type" : "Person",
    "name" : "King DJ",
    "url" : "https://www.hubdev.in/references/author/hubdev"
  },
  "dateModified" : "2025-11-23T20:30:42.635Z",
  "datePublished" : "2025-11-23T20:30:42.000Z",
  "headline" : "Building a clean HubSpot React Header & Footer — what we did and why it works",
  "image" : [ "https://www.hubdev.in/hubfs/Profile/Images/hubspot-react-header-footer-guide.png" ],
  "mainEntityOfPage" : {
    "@id" : "https://www.hubdev.in/references/hubspot-react-header-footer-guide",
    "@type" : "WebPage"
  },
  "publisher" : {
    "@type" : "Organization",
    "logo" : {
      "@type" : "ImageObject",
      "url" : "https://www.hubdev.in/hubfs/Profile/hub-dev-logo.png"
    },
    "name" : "Deets Engineering Space Station"
  }
}
```