SearchStax SearchStudio UX React Integration Guide for Drupal on Acquia Cloud


Dedicated Search Page & Optional Block

Overview

This guide walks you through setting up the SearchStax SearchStudio UX React UI Kit inside a Drupal site hosted on Acquia Cloud IDE. By the end, you will have a fully functional React-powered search page served from a custom Drupal module, with an optional Drupal Block variant.

What You Will Build

  • A Drupal custom module that provides a /searchstax route rendering a React search interface
  • An optional Drupal Block that mounts the same React app on any page via Layout Builder
  • A Vite + React app that bundles SearchStax widgets and CSS into static assets Drupal can load

Architecture at a Glance

You will maintain two codebases that work together:

  • React app (outside docroot) — built with Vite, outputs dist/app.js and dist/app.css
  • Drupal custom module (inside docroot) — defines a route, twig template, and library that loads those built assets

After every React build, you copy the output files into the Drupal module and clear caches.

Prerequisites

  • Acquia Cloud IDE/Local IDE equivalent access with your Drupal codebase checked out
  • SearchStax Site Search account with an App provisioned
  • Your SearchStax API credentials (search URL, suggester URL, track API key, auth token)
  • Basic familiarity with terminal commands, Drupal modules, and React

Step 1: Switch to Node 18+

Acquia Cloud IDEs often default to Node 14, which is too old for Vite and the SearchStax packages. You need Node 18 as an absolute minimum, but the current LTS (Node 20 or 22) is recommended.

Check your current version:

node -v

If this shows v14.x or v16.x (anything below 18), follow the steps below.

Install and activate a modern Node version using nvm:

nvm (Node Version Manager) is typically pre-installed on Acquia Cloud IDE. Run:

nvm install 18
nvm use 18
nvm alias default 18

This installs the latest LTS release and sets it as the default for future terminal sessions. Verify:

node -v   # Should show v20.x.x or v22.x.x

Tip: Node 20 or 22 will also work. Just replace 18 with your preferred version in the commands above. Any version 18 or higher is required by Vite.

Important: If nvm is not available, you can install it with:

curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.40.4/install.sh | bash

Then restart your terminal.


Step 2: Create the Directory Layout

Set up both the React app directory and the Drupal module directory.

Create the React app folder (outside docroot):

mkdir -p /home/ide/project/searchstax-ui
cd /home/ide/project/searchstax-ui

Create the Drupal module folder:

mkdir -p /home/ide/project/docroot/modules/custom/searchstax_search_page/{src/Controller,src/Plugin/Block,templates,dist}

Expected directory structure:

/home/ide/project/
├─ searchstax-ui/              ← React app (Vite)
│   ├─ src/
│   │   ├─ App.tsx
│   │   ├─ main.tsx
│   │   └─ searchstax/config.ts
│   ├─ .env.local
│   ├─ package.json
│   └─ vite.config.ts

└─ docroot/
    └─ modules/custom/searchstax_search_page/
        ├─ searchstax_search_page.info.yml
        ├─ searchstax_search_page.routing.yml
        ├─ searchstax_search_page.libraries.yml
        ├─ searchstax_search_page.module
        ├─ src/Controller/SearchPageController.php
        ├─ src/Plugin/Block/SearchstaxSearchBlock.php
        ├─ templates/searchstax-search-page.html.twig
        └─ dist/  (app.js, app.css copied here after build)

Step 3: Build the React App

3.1 Scaffold with Vite

cd /home/ide/project/searchstax-ui
npm create vite@latest . -- --template react-ts
npm install

3.2 React version compatibility

The searchstudio-ux-react package (v4.x) officially supports both React 18 and React 19. Vite will scaffold with the latest React version, and that will work fine. No need to pin or downgrade.

Tip: You can verify this yourself by running:

npm view @searchstax-inc/searchstudio-ux-react peerDependencies

Which returns react: ^18.0.0 || ^19.0.0.

3.3 Install SearchStax packages

npm install --save @searchstax-inc/searchstudio-ux-react @searchstax-inc/searchstudio-ux-js

Why both packages? The searchstudio-ux-react package provides the React components (SearchstaxWrapper, SearchstaxInputWidget, etc.). The searchstudio-ux-js package ships the default CSS theme. The React package does NOT include any CSS of its own — it depends on the JS package for all styling. Installing both ensures the theme stylesheet is available in your node_modules for Vite to bundle.

CSS clarification: Some older documentation references importing CSS from @searchstax-inc/searchstudio-ux-react/dist/styles/mainTheme.css. That path does not exist in the published React package. All current SearchStax documentation confirms the CSS lives exclusively in the JS package at @searchstax-inc/searchstudio-ux-js/dist/styles/mainTheme.css.

3.4 Import the CSS theme

The SearchStax React components have no built-in styles. All default styling comes from a CSS file shipped in the companion JS package. There are three ways to import it — pick whichever fits your setup best.

This is the simplest approach. Vite will see the CSS import and bundle it into your output app.css automatically.

// src/main.tsx
import { StrictMode } from "react"
import { createRoot } from "react-dom/client"

// SearchStax default theme — imported from the JS package
import "../node_modules/@searchstax-inc/searchstudio-ux-js/dist/styles/mainTheme.css"

// Your own overrides
import "./index.css"

import App from "./App.tsx"

// Change "root" to "searchstax-root" to match the Drupal twig template
createRoot(document.getElementById("searchstax-root")!).render(
  <StrictMode>
    <App />
  </StrictMode>,
)

Import order matters: Import the SearchStax theme before your own index.css. This way your custom styles take precedence over the defaults without needing !important.

Mount point: The fresh Vite install mounts on an element with id="root". You must change this to "searchstax-root" to match the <div> in your Drupal twig template (Step 8.4).

Option B: Import via CSS @import in index.css

If you prefer keeping all CSS imports in a stylesheet rather than in JavaScript:

/* src/index.css */
@import "../node_modules/@searchstax-inc/searchstudio-ux-js/dist/styles/mainTheme.css";

/* Your custom overrides below */
:root {
  font-family: system-ui, -apple-system, Segoe UI, Roboto, Arial, sans-serif;
  line-height: 1.5;
}

body {
  margin: 0;
}

Then in main.tsx you only need:

import "./index.css"

Option C: Import SCSS for deeper customization

The JS package also ships SCSS source files with variables and mixins you can override:

/* src/index.scss */
@import "../node_modules/@searchstax-inc/searchstudio-ux-js/dist/styles/scss/mainTheme.scss";

/* Override SCSS variables or add custom rules */

SCSS requirement: If using SCSS, install the sass package: npm install --save-dev sass. Vite supports SCSS natively once the package is present.

Overriding default styles

After importing the theme, you can override any of the default SearchStax CSS classes in your own stylesheet:

/* Example overrides in index.css */

.searchstax-search-input-container {
  max-width: 700px;
  margin: 0 auto;
}

.searchstax-search-result {
  border-bottom: 1px solid #eee;
  padding: 16px 0;
}

.searchstax-pagination-container {
  text-align: center;
  margin-top: 24px;
}

Tip: Use your browser DevTools to inspect the rendered widgets and find the exact class names you want to override. All SearchStax classes are prefixed with searchstax- making them easy to identify.

3.5 Clean up default Vite CSS

A fresh Vite install generates an index.css with extensive theming that will conflict with Drupal’s own theme. Replace the entire contents of src/index.css with a clean baseline:

:root {
  font-family: system-ui, -apple-system, Segoe UI, Roboto, Arial, sans-serif;
  line-height: 1.5;
}

body {
  margin: 0;
}

/* Add any SearchStax style overrides here */

Specifically, the fresh Vite index.css includes these patterns that must be removed:

/* REMOVE ALL OF THESE — they conflict with Drupal theming */
color-scheme: light dark;
@media (prefers-color-scheme: dark) {}
--text, --bg, --accent, etc.
#root { display: flex; ... }
h1, h2, p, code {}

Tip: The safest approach is to delete everything in index.css and start fresh with just the baseline above. Drupal’s theme will handle global typography and colors.


Step 4: Configure Environment Variables

Create a .env.local file in the React app root. Vite only exposes variables prefixed with VITE_.

File: /home/ide/project/searchstax-ui/.env.local

VITE_SS_LANGUAGE=en
VITE_SS_SEARCH_URL=https://YOUR-SEARCHSTAX-ENDPOINT/empower/select
VITE_SS_SUGGESTER_URL=https://YOUR-SUGGEST-ENDPOINT/empower/suggest
VITE_SS_TRACK_API_KEY=your_track_api_key_here
VITE_SS_AUTH_TYPE=token
VITE_SS_SEARCH_AUTH=your_read_only_token_here

Important: Replace the placeholder values with your actual SearchStax credentials from the Site Search App Settings screen.

Troubleshooting 401 errors: A 401 response from SearchStax almost always means one of: wrong token value, wrong auth type (basic vs. token), token lacks read permissions, or incorrect endpoint URL.


Step 5: Write the React Application Code

5.1 Configuration helper — src/searchstax/config.ts

export function getSearchStaxUiConfig() {
  return {
    language: import.meta.env.VITE_SS_LANGUAGE || "en",
    searchURL: import.meta.env.VITE_SS_SEARCH_URL || "",
    suggesterURL: import.meta.env.VITE_SS_SUGGESTER_URL || "",
    trackApiKey: import.meta.env.VITE_SS_TRACK_API_KEY || "",
    searchAuth: import.meta.env.VITE_SS_SEARCH_AUTH || "",
    authType: import.meta.env.VITE_SS_AUTH_TYPE || "token",
    router: {
      enabled: true,
      routeName: "searchstax",
      ignoredKeys: [],
    },
  };
}

5.2 Entry point — src/main.tsx

Use the main.tsx from Step 3.4 (Option A is recommended). That file already covers the SearchStax CSS import, the mount point change, and the App render call.

5.3 App component — src/App.tsx

import {
  SearchstaxWrapper,
  SearchstaxInputWidget,
  SearchstaxResultWidget,
} from "@searchstax-inc/searchstudio-ux-react";
import { getSearchStaxUiConfig } from "./searchstax/config";

export default function App() {
  const cfg = getSearchStaxUiConfig();

  return (
    <SearchstaxWrapper
      language={cfg.language}
      searchURL={cfg.searchURL}
      suggesterURL={cfg.suggesterURL}
      trackApiKey={cfg.trackApiKey}
      searchAuth={cfg.searchAuth}
      authType={cfg.authType}
      router={{
        enabled: cfg.router.enabled,
        routeName: cfg.router.routeName,
        ignoredKeys: cfg.router.ignoredKeys,
        title: (result: any) => `Search results for: ${result.query}`,
      }}
      initialized={() => {}}
    >
      <SearchstaxInputWidget />
      <SearchstaxResultWidget afterLinkClick={(result: any) => result} />
    </SearchstaxWrapper>
  );
}

Tip: You can add more widgets later: SearchstaxFacetsWidget, SearchstaxSortingWidget, SearchstaxPaginationWidget, SearchstaxOverviewWidget. Each is imported from @searchstax-inc/searchstudio-ux-react.


Step 6: Configure Vite for Stable Output Filenames

Drupal needs predictable filenames (app.js, app.css). Configure Vite to produce them:

File: vite.config.ts

import { defineConfig } from "vite";
import react from "@vitejs/plugin-react";

export default defineConfig({
  plugins: [react()],
  build: {
    rollupOptions: {
      output: {
        entryFileNames: "app.js",
        chunkFileNames: "chunks/[name]-[hash].js",
        assetFileNames: (assetInfo) => {
          if (assetInfo.name?.endsWith(".css")) return "app.css";
          return "assets/[name]-[hash][extname]";
        },
      },
    },
  },
});

Version compatibility: Use Vite 5.x or 6.x with @vitejs/plugin-react 4.x. If Vite scaffolded a newer major version, check that your plugin version is compatible to avoid peer dependency mismatches.


Step 7: Build and Deploy Assets

Build the React app:

cd /home/ide/project/searchstax-ui
npm run build

This produces dist/app.js and dist/app.css.

Copy built assets into the Drupal module:

cp dist/app.js  /home/ide/project/docroot/modules/custom/searchstax_search_page/dist/app.js
cp dist/app.css /home/ide/project/docroot/modules/custom/searchstax_search_page/dist/app.css

Step 8: Create the Drupal Custom Module

8.1 Module info file

searchstax_search_page.info.yml

name: SearchStax Search Page
type: module
description: "Dedicated SearchStax React search page"
core_version_requirement: ^10 || ^11
package: Custom

8.2 Routing

searchstax_search_page.routing.yml

searchstax_search_page.page:
  path: "/searchstax"
  defaults:
    _controller: '\Drupal\searchstax_search_page\Controller\SearchPageController::view'
    _title: "Search"
  requirements:
    _permission: "access content"

8.3 Controller

src/Controller/SearchPageController.php

<?php

namespace Drupal\searchstax_search_page\Controller;

use Drupal\Core\Controller\ControllerBase;
use Symfony\Component\HttpFoundation\Request;

final class SearchPageController extends ControllerBase {

  public function view(Request $request): array {
    $language = $this->languageManager()->getCurrentLanguage()->getId();
    $path = $request->getPathInfo();
    $query = $request->query->all();

    return [
      '#theme' => 'searchstax_search_page',
      '#attached' => [
        'library' => [
          'searchstax_search_page/search_app',
        ],
      ],
      '#drupal_language' => $language,
      '#drupal_path' => $path,
      '#drupal_query' => json_encode($query),
    ];
  }

}

8.4 Twig template

templates/searchstax-search-page.html.twig

<div
  id="searchstax-root"
  data-drupal-language="{{ drupal_language }}"
  data-drupal-path="{{ drupal_path }}"
  data-drupal-query="{{ drupal_query|e('html_attr') }}"
></div>

8.5 Theme hook

searchstax_search_page.module

<?php

/**
 * Implements hook_theme().
 */
function searchstax_search_page_theme($existing, $type, $theme, $path) {
  return [
    'searchstax_search_page' => [
      'variables' => [
        'drupal_language' => NULL,
        'drupal_path' => NULL,
        'drupal_query' => NULL,
      ],
      'template' => 'searchstax-search-page',
    ],
  ];
}

8.6 Libraries definition

searchstax_search_page.libraries.yml

search_app:
  css:
    theme:
      dist/app.css: { minified: true }
  js:
    dist/app.js: { minified: true }
  dependencies:
    - core/drupal
    - core/drupalSettings

8.7 Enable the module and clear caches

cd /home/ide/project/docroot
drush en searchstax_search_page -y
drush cr

Visit /searchstax on your site. You should see the search interface rendered.


Step 9: Optional — Add a Drupal Block

This lets you embed the same search interface on any page via Layout Builder or the Block UI.

src/Plugin/Block/SearchstaxSearchBlock.php

<?php

namespace Drupal\searchstax_search_page\Plugin\Block;

use Drupal\Core\Block\BlockBase;

/**
 * Provides a SearchStax React search block.
 *
 * @Block(
 *   id = "searchstax_search_block",
 *   admin_label = @Translation("SearchStax Search (React)"),
 *   category = @Translation("Search")
 * )
 */
final class SearchstaxSearchBlock extends BlockBase {

  public function build(): array {
    return [
      '#theme' => 'searchstax_search_page',
      '#attached' => [
        'library' => ['searchstax_search_page/search_app'],
      ],
      '#drupal_language' => \Drupal::languageManager()->getCurrentLanguage()->getId(),
      '#drupal_path' => \Drupal::request()->getPathInfo(),
      '#drupal_query' => json_encode(\Drupal::request()->query->all()),
    ];
  }

}

Multiple blocks caveat: If you place more than one instance of this block on the same page, change the mount ID to be unique (e.g., searchstax-root-{{ random() }}) and update main.tsx to use querySelectorAll to mount on all matching nodes.


Step 10: Repeatable Build/Deploy Workflow

Every time you make changes to the React app, repeat this workflow:

# 1. Build the React app
cd /home/ide/project/searchstax-ui
npm run build

# 2. Copy dist into Drupal module
cp dist/app.js  /home/ide/project/docroot/modules/custom/searchstax_search_page/dist/app.js
cp dist/app.css /home/ide/project/docroot/modules/custom/searchstax_search_page/dist/app.css

# 3. Clear Drupal caches
cd /home/ide/project/docroot
drush cr

Tip: Consider creating a simple shell script (e.g., deploy.sh) that runs all three steps so you can just run ./deploy.sh during development.


Troubleshooting

Nothing renders on the page

  • Verify the twig template contains <div id="searchstax-root"></div>
  • Confirm the library is attached — check that the module name in libraries.yml matches the .info.yml filename
  • Confirm dist/app.js exists in the module’s dist/ folder
  • Run drush cr to clear caches
  • In browser DevTools console, look for the log message: "SearchStax React bundle loaded"

React hooks error (useState is null)

This almost always means multiple React versions are installed side by side. Run:

npm ls react

You should see only one version. If you see two, remove node_modules and package-lock.json, ensure your package.json has a single React version, and run npm install again.

Vite / plugin peer dependency mismatch

If you see peer dependency warnings between Vite and @vitejs/plugin-react, ensure your Vite major version is compatible with your plugin version. For example, Vite 5.x works with plugin-react 4.x.

CSS theme not applying

Verify that @searchstax-inc/searchstudio-ux-js is installed (check node_modules). The correct import path is:

@searchstax-inc/searchstudio-ux-js/dist/styles/mainTheme.css

Do not use @searchstax-inc/searchstudio-ux-react/dist/styles/mainTheme.css — that path does not exist.


  • Add a URL/path field to your Search API index so result titles link to the correct Drupal node path
  • Enable highlighting in your SearchStax configuration to show better search snippets
  • Add more widgets for a richer UX: SearchstaxFacetsWidget, SearchstaxSortingWidget, SearchstaxPaginationWidget, SearchstaxOverviewWidget
  • Customize the CSS theme by overriding the default SearchStax classes in your index.css

References