Configuration
The App Shell app-shell.config.ts configuration file holds the application’s configuration.
It expects a default export of an object that adheres to the HvAppShellConfig interface.
You can find the configuration properties documentation below.
// app-shell.config.ts
import type { HvAppShellConfig } from "@hitachivantara/app-shell-vite-plugin";
export default {
name: "App Name",
baseUrl: "/",
navigationMode: "ONLY_LEFT",
apps: {
"@hv-apps/my-app/": "https://example.com:3001/",
"@hv-apps/another-app/": "https://example.com:5001/",
"@hv-apps/other-app/someFile.js": "https://example.com:6001/someFile.js",
"@hv-apps/other-app/entryPoint": "https://example.com:7001/index.js",
},
// ...
} satisfies HvAppShellConfig;Module locations
Properties that reference modules’ locations can be:
- A bare-specifier, such as
@hv-apps/my-app/pages/Hello.js. The bare-specifier prefix must be mapped to a URL (baseUrl) in theappsproperty. - A fully qualified URL, like
http://localhost:3001/pages/Hello.js, which directly points to the module’s location.
Note: During development, the bare-specifier with a @self prefix (eg. @self/pages/Hello.js) can also be used, referring to the module within the current Application Bundle.
Module structure
The App Shell configuration file uses a common structure to declare modules, which be found in mainPanel views, header actions, or providers.
The common structure has the following specification:
bundle: a string with the module location.config: an optional properties object, passed to the module when it is loaded.
Example:
const moduleConfig = {
bundle: "@hv-apps/my-app/pages/Hello.js",
config: {
name: "John",
age: 30,
},
};// Hello.tsx
export default function Hello({ name, age }) {
return <h1>{`Hello ${name} (${age})!`}</h1>;
}Internationalization
Properties that receive text values can be internationalized by using a key that is present on the bundle of the translations property.
If a value, used in any of the internationalizable properties, is not present on the translations bundle then it will be used as is.
Configuration properties
The HvAppShellConfig configuration has the following properties:
name
Specifies the product name displayed in the header and in the browser tab. Supports internationalization.
baseUrl
The base path for the product, required when it isn’t hosted at the root of the hosting service. Defaults to "/" if not present.
logo
Defines the product logo. It includes:
name: EitherHITACHI,LUMADA, orPENTAHO. Defaults toHITACHI.description: The descriptive text of the logo, that is also subject to internationalization.
If you don’t want to add a logo to the header, explicitly set the menu item logo to null.
apps
Key-value object of Application Bundles IDs and their respective locations. Used by the App Shell to generate the importmap and thus allowing the import of ES Modules from different Application Bundles.
Example of the apps object:
const apps = {
"@hv-apps/an-app": "http://localhost:3001/",
"@hv-apps/another-app": "http://localhost:5001/",
};Result in the importmap:
<script type="importmap">
{
"imports": {
"@hv-apps/an-app/": "http://localhost:3001/",
"@hv-apps/another-app/": "http://localhost:5001/"
}
}
</script>As per the example above, the apps keys will have a trailing forward slash concatenated to it by default, added by the App Shell Vite plugin. In order to provide more control to the developer of the app, the disableAppsKeyNormalization parameter of the App Shell Vite plugin can be used to disable this behavior.
In order to reference Views and Shared Modules from other Application Bundles’, one must be registered in the App Shell’s configuration.
When registered, the Application Bundle’s contents can then be referenced as “subpaths” of its module ID.
const views = [
{ route: "/hello", bundle: "@hv-apps/another-app/pages/Other.js" },
];import { getName } from "@hv-apps/another-app/modules/nameGenerator.js";
export default function Hello() {
return <h1>Hello {getName()}!</h1>;
}mainPanel
Defines the main panel content properties. Accepts the following:
mainPanel.views
An array of View items that will be displayed in the main panel, extending the base module structure with the following properties:
route: Defines the route path of the View.views: Array of nested View items. route will be appended to the parent route. See Nested Views for more information.
Note: The mainPanel and non-nested views accept any of the HvContainer props, for configuring the container that wraps the Views.
The maxWidth prop defaults to "xl", instead of false as in the UI Kit.
Example:
const mainPanel = {
maxWidth: "sm",
views: [
{
bundle: "@hv-apps/my-app/pages/Hello.js",
route: "/hello",
config: {
name: "John",
},
},
{
bundle: "@hv-apps/another-app/pages/Person.js",
route: "/contacts/:name",
maxWidth: "lg",
fixed: true,
},
],
};See Routing for more information.
menu
Describes the Product’s menus, with each item potentially having submenus and associated icons. It’s an array where each menu item is defined by the following set of values:
label: The menu label (visible by the users at the browser). Required. Supports internationalization.target: The route value defined at the view.icon: The icon associated with the app.iconType: Type of icon to be used (at this moment the only possible value isuikit).name: Name of the icon to be used (as identified at UI Kit’s icons library)
submenus: Nested/recursive array ofmenuitems.conditions: Array of condition objects. Menu items with an associated target view are implicitly subject to any conditions declared by the view’s configuration element. These are composed with any conditions locally declared in the menu configuration element.
WARNING:
targetandsubmenusproperties should not be used together in the definition of a menu item.
To give more context, when we use the Vertical Navigation panel and perform a menu item click, it needs to know if it should navigate to the given target or open its submenu tree. Since this is done by explicitly checking which property exists. Also, when navigating to a URL, App Shell will try its best to find which menu should be selected and as such, having both target and submenus property can lead to behaviour inconsistency.
Example:
const menu = [
{
label: "Page 1",
icon: { iconType: "uikit", name: "Open" },
submenus: [
{
label: "Sub Page 1",
target: "/subpage1",
icon: { iconType: "uikit", name: "Close" },
},
],
},
{
label: "Page 2",
target: "/page2",
},
];See Navigation for more information.
navigationMode
Determines the layout of the navigation. The possible options are:
TOP_AND_LEFT: In this mode, the first level of menu items will be displayed on the top (inside the header), and the remaining items will be displayed inside a vertical navigation panel on the left.ONLY_TOP: In this mode all navigation will be presented on the top. With this mode, only two levels of menus will be displayed: the first one inside the header, and the second one will appear below the header on an extra navigation bar.ONLY_LEFT: In this mode, all the menu structure will be displayed on the left vertical navigation panel. No items will appear on the top.
header
Defines all customizations that can be applied to the Header of the App Shell:
header.actions
Header Actions that will be displayed on the Header. It follows the module structure object API.
Example:
const header = {
actions: [
{
bundle: "@hv/user-notifications-client/index.js",
config: {
showCount: false,
},
},
{
bundle: "@hv/theming-client/colorModeSwitcher.js",
},
],
};See Header actions for more information and see the available App Shell built-in actions.
systemProviders
This prop defines the System Providers that will wrap the entire App Shell application, including all Views, Shared Modules, and regular Providers. System providers are positioned at the top of the React component tree and provide context that should be available throughout the entire application.
System providers follow the module structure object API and do not have an associated condition.
const systemProviders = [
{ bundle: "@hv-apps/auth-app/providers/AuthProvider.js" },
{
bundle: "@hv-apps/config-app/providers/ConfigProvider.js",
config: {
apiUrl: "https://api.example.com",
},
},
];Notes:
- The App Shell instantiates System Providers in their declaration order, but dependencies between System Providers should be avoided and you should never rely on the existence of another System Provider.
- Each Application Bundle should expose just one System Provider, though if multiple are needed they can be composed into a single System Provider.
Key Differences from Regular Providers:
- Position in tree: System providers sit at the very top of the component tree, while regular providers are nested deeper just before navigation and views.
- Loading behavior: System providers are always loaded and cannot have an associated condition, whereas regular providers can be conditionally loaded.
- Evaluation order: System providers are evaluated first in their declaration order, followed by regular providers, which ensures their context is available when conditions are evaluated.
- Context availability: Only conditions and regular providers benefit from this distinction (system providers’ context is accessible to both conditions and regular providers, but regular providers’ context is not accessible to conditions). Both types are equally accessible to views and other modules.
[!IMPORTANT] If a condition hook needs context from a provider, that provider must be a system provider to ensure it is present when conditions are evaluated. Use system providers thoughtfully, as they exist regardless of conditional logic and are available to the entire application, on the other hand, regular providers are subject to other conditional providers logic (reloads).
providers
This prop defines the Providers that will wrap all the Views and Shared Modules. Unlike system providers, these can be conditionally loaded based on conditions, but are not accessible to condition hooks. They follow the module structure object API.
const providers = [
{ bundle: "@hv-apps/some-app/providers/SomeProvider.js" },
{
bundle: "@hv-apps/other-app/providers/OtherProvider.js",
config: {
someConfig: "value",
},
},
{
bundle: "@hv-apps/feature-app/providers/FeatureProvider.js",
conditions: [
{
bundle: "@hv-apps/feature-app/conditions/useFeatureEnabled.js",
},
],
},
];Notes:
- Each Application Bundle should expose just one Provider, though if multiple are needed they can be composed into a single Provider.
- The App Shell instantiates Providers in their declaration order, but dependencies between Providers should be avoided and you should never rely on the existence of another Provider.
- Providers’ context is not accessible to conditions (if a condition hook needs context from a provider, you must use a system provider instead).
- Don’t abuse this feature: context that’s only needed for one View shouldn’t be placed in a global Provider just because another View needs it. For example, the i18next provider should be instantiated per view using a HOC rather than as a global provider for each Application Bundle.
- Providers can be conditionally loaded using the
conditionsproperty.
conditions
Conditions are bundles that control whether certain elements (such as views, menu items, header actions, providers, or services) should be displayed or loaded in the final App Shell Model, providing a powerful mechanism for conditional rendering based on dynamic runtime criteria.
How Conditions Work
A condition is a Shared Module that exports a React hook as its default export. This hook must return an asynchronous result object with the following structure:
interface UseConditionResult {
isPending: boolean; // Whether the condition is still being evaluated
error: Error | null; // Any error that occurred during evaluation
result: boolean | undefined; // The condition result (undefined when pending or on error)
}The result is a discriminated union type that can be in one of three states: pending, error, or success.
Using Conditions
Conditions can be applied to various configuration elements by adding a conditions array:
// Example: Conditional view
{
bundle: "@hv-apps/my-app/pages/AdminPanel.js",
route: "/admin",
conditions: [
{
bundle: "@hv-apps/my-app/conditions/useIsAdmin.js",
},
{
bundle: "@hv-apps/my-app/conditions/useFeatureEnabled.js",
config: {
featureFlag: "admin-panel",
},
},
],
}Condition Behavior
- Multiple conditions: When multiple conditions are present, ALL must return
truefor the element to be included in the configuration (AND logic) - Pending state: The associated configuration element is excluded from the configuration while any of its conditions is in a pending state
- Error handling: If a condition returns an error, the associated configuration element is excluded from the configuration
- Menu conditions: Menu configuration elements with an associated target view are implicitly subject to any conditions declared by the view’s configuration element. These are composed with any conditions locally declared in the menu configuration element.
Example Condition Implementation
Synchronous condition:
// useIsAdmin.ts
import type { UseConditionResult } from "@hitachivantara/app-shell-shared";
const useIsAdmin = (): UseConditionResult => {
const user = useCurrentUser(); // Possibly supported by a system provider context
return {
isPending: false,
error: null,
result: user?.role === "admin",
};
};
export default useIsAdmin;Conditions can access context provided by systemProviders, which is useful when conditions need to evaluate based on authentication state, user permissions, or global configuration. In the example above, useCurrentUser() would be accessing context from an authentication system provider.
Asynchronous condition:
// useFeatureEnabled.ts
import { useEffect, useState } from "react";
import type { UseConditionResult } from "@hitachivantara/app-shell-shared";
interface FeatureConfig {
featureFlag: string;
}
const useFeatureEnabled = (config?: FeatureConfig): UseConditionResult => {
const [isPending, setIsPending] = useState(true);
const [result, setResult] = useState(false);
const [error, setError] = useState();
useEffect(() => {
if (!config?.featureFlag) {
setIsPending(false);
return;
}
fetch(`/api/features/${config.featureFlag}`)
.then((res) => res.json())
.then((data) => {
setResult(data.enabled);
setIsPending(false);
})
.catch(() => {
setIsPending(false);
setError("Error fetching feature flag");
});
}, [config?.featureFlag]);
return {
isPending,
error,
result,
};
};
export default useFeatureEnabled;services
This prop defines the Services that are to be handled by the @hitachivantara/app-shell-services package.
The services property is a key-value object, where the key is the service identifier and the value is an array of supported service definitions:
- Instance Service: Directly provides an instance (
value), or references a module that exports the instance (bundle). - Factory Service: Provides a factory function (
value), or references a module that exports a factory function (bundle). The factory function is called with aconfigobject if provided to produce the service instance. - Component Service: Provides a React component (
value), or references a module exporting a React component (bundle). The component is used as-is or bound with a config object if provided.
Each service definition can be configured in one of two ways:
-
Direct Value:
{ value: ... }The value is provided directly in the configuration. -
Bundle Reference:
{ bundle: "...", config?: {...} }The bundle follows the module structure object API that exports the required value (instance, factory function, or React component). The optional config is passed to the module when loaded.
A ranking property is used to determine the order of precedence when multiple implementations are available. Higher ranking values indicate higher precedence.
Services support conditions to control their availability dynamically:
const appShellConfig = {
services: {
"my-app/services:UseCreateAction": [
// Instance - direct value
{
instance: {
value: {
key1: "some value",
key2: 11,
key3: { nested: "value" },
},
},
ranking: 10,
},
// Instance - module reference with conditions
{
instance: {
bundle: "some-app/actions/create/useCreateReportAction.js",
},
ranking: 50,
conditions: [
{
bundle: "@hv-apps/my-app/conditions/useHasReportPermission.js",
},
],
},
// Factory - module reference
{
factory: {
bundle: "some-app/factories/factory.js",
config: { option: true },
},
},
// Component - module reference with conditions
{
component: {
bundle: "@my-app/components/HelloComponent.js",
config: { name: "John" },
},
ranking: 50,
conditions: [
{
bundle: "@hv-apps/my-app/conditions/useFeatureEnabled.js",
config: { featureFlag: "hello-component" },
},
],
},
],
},
};translations
This property defines bundles of translations that need to be added in runtime to App Shell translation bundle. These are NOT available to the Views.
Being an object, it is required to have as root the language (en, pt, etc.) of the bundle:
const config = {
name: "translationKey",
logo: {
name: "HITACHI",
description: "logoDesc",
},
menu: [
{
label: "pageOne",
target: "/page1",
},
],
translations: {
en: {
translationKey: "An Amazing App",
logoDesc: "Company logo",
pageOne: "Page One",
},
pt: {
translationKey: "Uma App Fantástica",
logoDesc: "Logo da empresa",
pageOne: "Página Um",
},
},
};In the example above, the name of the application, that appears on the browser tab and on the header, defines a key that exists on the translations bundle and so will be translated by App Shell accordingly. The same happens with other localized properties (like the logo description or menu labels).
Application Bundles may use different localization libraries, but using i18next is the recommendation. The App Shell does not provide translations to the embedded Views meaning that each Application Bundle must handle its own translations.
A View may accidentally access the App Shell’s i18next instance when using the useTranslation hook.
Because of that, Application Bundles must ensure they use its own i18next instance to avoid collision and incorrect information display.
For more information check the documentation at i18next.com/overview/api .
theming
This property defines the theme of the Product, and it supports both the base UI Kit themes and custom themes.
The config object for it has the following options:
theme: The active theme. It is the name of the built-in themes, or the module name.colorMode: The color mode of the theme ("light"or"dark"). Defaults totheme’s default color mode.
Example:
const theming = {
theme: "@hv-apps/my-app/tatooine.js",
colorMode: "light",
};Custom theme
Custom themes are a Shared Module that exports a UI Kit theme definition. For more information on theme structures, refer to the UI Kit theming documentation.
Env variables
The configuration can use environment variables that are to be replaced at build time.
const { VITE_USER_NOTIFICATIONS_URL = "http://localhost:8080" } = process.env;
const apps = {
"@hv/user-notifications-client": VITE_USER_NOTIFICATIONS_URL,
};These variables should be set at .env files like explained here .