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
/searchstaxroute 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.jsanddist/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
18with your preferred version in the commands above. Any version 18 or higher is required by Vite.
Important: If
nvmis not available, you can install it with:curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.40.4/install.sh | bashThen 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 peerDependenciesWhich 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.
Option A: Import in main.tsx (recommended)
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
sasspackage: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.cssand 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 (
basicvs.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-react4.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 updatemain.tsxto usequerySelectorAllto 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.shduring 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.ymlmatches the.info.ymlfilename - Confirm
dist/app.jsexists in the module’sdist/folder - Run
drush crto 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.
Recommended Next Steps
- 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