diff --git a/dev-server/app.tsx b/dev-server/app.tsx
new file mode 100644
index 0000000000..4f4cc2fc05
--- /dev/null
+++ b/dev-server/app.tsx
@@ -0,0 +1,269 @@
+// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
+// SPDX-License-Identifier: Apache-2.0
+
+import React, { createContext, Suspense, useContext, useEffect, useState } from 'react';
+
+import ErrorBoundary from './error-boundary';
+import { defaultURLParams, formatURLParams, URLParams } from './url-params';
+
+/**
+ * App context for sharing URL parameters and page state
+ */
+export interface AppContextType {
+ pageId?: string;
+ urlParams: URLParams;
+ isServer: boolean;
+ setUrlParams: (newParams: Partial) => void;
+}
+
+const AppContext = createContext({
+ pageId: undefined,
+ urlParams: defaultURLParams,
+ isServer: true,
+ setUrlParams: () => {},
+});
+
+export const useAppContext = () => useContext(AppContext);
+
+/**
+ * Props for the App shell component
+ */
+export interface AppProps {
+ pageId?: string;
+ urlParams: URLParams;
+ isServer: boolean;
+ children: React.ReactNode;
+}
+
+/**
+ * Check if a page is an AppLayout page (needs special handling)
+ */
+function isAppLayoutPage(pageId?: string): boolean {
+ if (!pageId) {
+ return false;
+ }
+ const appLayoutPages = [
+ 'app-layout',
+ 'content-layout',
+ 'grid-navigation-custom',
+ 'expandable-rows-test',
+ 'container/sticky-permutations',
+ 'copy-to-clipboard/scenario-split-panel',
+ 'prompt-input/simple',
+ 'funnel-analytics/static-single-page-flow',
+ 'funnel-analytics/static-multi-page-flow',
+ 'charts.test',
+ 'error-boundary/demo-async-load',
+ 'error-boundary/demo-components',
+ ];
+ return appLayoutPages.some(match => pageId.includes(match));
+}
+
+/**
+ * Theme switcher component with toggles for dark mode, density, motion, etc.
+ */
+function ThemeSwitcher({
+ urlParams,
+ setUrlParams,
+}: {
+ urlParams: URLParams;
+ setUrlParams: (p: Partial) => void;
+}) {
+ const switcherStyle: React.CSSProperties = {
+ display: 'flex',
+ gap: '12px',
+ alignItems: 'center',
+ fontSize: '12px',
+ };
+
+ const labelStyle: React.CSSProperties = {
+ display: 'flex',
+ alignItems: 'center',
+ gap: '4px',
+ cursor: 'pointer',
+ };
+
+ return (
+
+
+ {
+ const newVisualRefresh = e.target.checked;
+ const updatedParams = { ...urlParams, visualRefresh: newVisualRefresh };
+ // Update URL first, then reload
+ const currentPath = window.location.pathname;
+ const newUrl = `${currentPath}${formatURLParams(updatedParams)}`;
+ window.location.href = newUrl;
+ }}
+ />
+ Visual refresh
+
+
+ setUrlParams({ mode: e.target.checked ? 'dark' : 'light' })}
+ />
+ Dark mode
+
+
+ setUrlParams({ density: e.target.checked ? 'compact' : 'comfortable' })}
+ />
+ Compact mode
+
+
+ setUrlParams({ motionDisabled: e.target.checked })}
+ />
+ Disable motion
+
+
+ );
+}
+
+/**
+ * Header component for the demo pages
+ */
+function Header({
+ sticky,
+ urlParams,
+ setUrlParams,
+}: {
+ sticky?: boolean;
+ urlParams: URLParams;
+ setUrlParams: (p: Partial) => void;
+}) {
+ const headerStyle: React.CSSProperties = {
+ boxSizing: 'border-box',
+ background: '#232f3e',
+ paddingBlockStart: '12px',
+ paddingBlockEnd: '11px',
+ paddingInline: '12px',
+ fontSize: '15px',
+ fontWeight: 700,
+ lineHeight: '17px',
+ display: 'flex',
+ color: '#eee',
+ justifyContent: 'space-between',
+ alignItems: 'center',
+ ...(sticky && {
+ inlineSize: '100%',
+ zIndex: 1000,
+ insetBlockStart: 0,
+ position: 'sticky' as const,
+ }),
+ };
+
+ const linkStyle: React.CSSProperties = {
+ textDecoration: 'none',
+ color: '#eee',
+ };
+
+ return (
+
+ );
+}
+
+/**
+ * Client-side effects for applying global styles and modes
+ * These only run on the client after hydration
+ */
+function ClientEffects({ urlParams }: { urlParams: URLParams }) {
+ useEffect(() => {
+ // Apply mode (light/dark)
+ import('@cloudscape-design/global-styles').then(({ applyMode, Mode }) => {
+ applyMode(urlParams.mode === 'dark' ? Mode.Dark : Mode.Light);
+ });
+ }, [urlParams.mode]);
+
+ useEffect(() => {
+ // Apply density
+ import('@cloudscape-design/global-styles').then(({ applyDensity, Density }) => {
+ applyDensity(urlParams.density === 'compact' ? Density.Compact : Density.Comfortable);
+ });
+ }, [urlParams.density]);
+
+ useEffect(() => {
+ // Apply motion disabled
+ import('@cloudscape-design/global-styles').then(({ disableMotion }) => {
+ disableMotion(urlParams.motionDisabled);
+ });
+ }, [urlParams.motionDisabled]);
+
+ useEffect(() => {
+ // Apply direction
+ document.documentElement.setAttribute('dir', urlParams.direction);
+ }, [urlParams.direction]);
+
+ return null;
+}
+
+/**
+ * App shell component that wraps demo pages
+ * Similar to pages/app/index.tsx but for SSR
+ */
+export default function App({ pageId, urlParams: initialUrlParams, isServer, children }: AppProps) {
+ const [urlParams, setUrlParamsState] = useState(initialUrlParams);
+ const isAppLayout = isAppLayoutPage(pageId);
+ const ContentTag = isAppLayout ? 'div' : 'main';
+
+ // Update URL params and sync to URL
+ const setUrlParams = (newParams: Partial) => {
+ const updatedParams = { ...urlParams, ...newParams };
+ setUrlParamsState(updatedParams);
+
+ // Update URL without reload (except for visualRefresh which needs reload)
+ if (!isServer && !('visualRefresh' in newParams)) {
+ const newUrl = pageId ? `/${pageId}${formatURLParams(updatedParams)}` : `/${formatURLParams(updatedParams)}`;
+ window.history.replaceState({}, '', newUrl);
+ }
+ };
+
+ // Header is always rendered outside the error boundary so users can still
+ // toggle settings (like visual refresh) even when a page throws an error
+ const header = (
+
+ );
+
+ // Page content is wrapped in error boundary (client-side only)
+ const pageContent = isServer ? children : {children} ;
+
+ // Wrap in Suspense on client side only (not supported in React 16 SSR)
+ const suspenseWrapped = isServer ? (
+ pageContent
+ ) : (
+ Loading...}>{pageContent}
+ );
+
+ return (
+
+ {/* Client-side effects for applying global styles */}
+ {!isServer && }
+
+
+ {header}
+ {suspenseWrapped}
+
+
+ );
+}
diff --git a/dev-server/collect-styles.mjs b/dev-server/collect-styles.mjs
new file mode 100644
index 0000000000..43b41f53cf
--- /dev/null
+++ b/dev-server/collect-styles.mjs
@@ -0,0 +1,70 @@
+// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
+// SPDX-License-Identifier: Apache-2.0
+
+import { glob } from 'glob';
+import fs from 'node:fs';
+import path from 'node:path';
+import { fileURLToPath } from 'node:url';
+
+const __dirname = path.dirname(fileURLToPath(import.meta.url));
+const rootDir = path.resolve(__dirname, '..');
+const componentsDir = path.resolve(rootDir, 'lib/components');
+
+let cachedStyles = null;
+
+/**
+ * Gets the global styles CSS from @cloudscape-design/global-styles
+ */
+function getGlobalStyles() {
+ try {
+ // Find the global-styles package in node_modules
+ const globalStylesPath = path.resolve(rootDir, 'node_modules/@cloudscape-design/global-styles/index.css');
+ return fs.readFileSync(globalStylesPath, 'utf-8');
+ } catch (e) {
+ console.warn('Warning: Could not read global styles:', e.message);
+ return '';
+ }
+}
+
+/**
+ * Collects all scoped CSS files from the built component library
+ * and combines them into a single CSS string for SSR injection.
+ * Also includes global styles from @cloudscape-design/global-styles.
+ */
+export function collectStyles() {
+ // Return cached styles if available (for performance)
+ if (cachedStyles !== null) {
+ return cachedStyles;
+ }
+
+ // Start with global styles
+ const globalStyles = getGlobalStyles();
+
+ const cssFiles = glob.sync('**/*.scoped.css', {
+ cwd: componentsDir,
+ absolute: true,
+ });
+
+ const componentStyles = cssFiles
+ .map(file => {
+ try {
+ return fs.readFileSync(file, 'utf-8');
+ } catch (e) {
+ console.warn(`Warning: Could not read CSS file ${file}:`, e.message);
+ return '';
+ }
+ })
+ .filter(Boolean)
+ .join('\n');
+
+ // Combine global styles first, then component styles
+ cachedStyles = globalStyles + '\n' + componentStyles;
+ return cachedStyles;
+}
+
+/**
+ * Clears the cached styles (useful for development when styles change)
+ */
+export function clearStyleCache() {
+ cachedStyles = null;
+}
diff --git a/dev-server/dev.mjs b/dev-server/dev.mjs
new file mode 100644
index 0000000000..e66706fccd
--- /dev/null
+++ b/dev-server/dev.mjs
@@ -0,0 +1,129 @@
+// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
+// SPDX-License-Identifier: Apache-2.0
+
+/**
+ * Development startup script that:
+ * 1. Starts the build watcher (gulp watch)
+ * 2. Waits for the initial build to complete
+ * 3. Starts the demo server
+ * 4. Handles graceful shutdown
+ */
+
+import { spawn } from 'node:child_process';
+import path from 'node:path';
+import { fileURLToPath } from 'node:url';
+
+const __dirname = path.dirname(fileURLToPath(import.meta.url));
+const rootDir = path.resolve(__dirname, '..');
+
+// Track child processes for cleanup
+const childProcesses = [];
+
+/**
+ * Spawns a child process and tracks it for cleanup
+ */
+function spawnProcess(command, args, options = {}) {
+ const proc = spawn(command, args, {
+ stdio: 'inherit',
+ shell: true,
+ cwd: rootDir,
+ ...options,
+ });
+
+ childProcesses.push(proc);
+
+ proc.on('error', err => {
+ console.error(`Error starting ${command}:`, err.message);
+ });
+
+ return proc;
+}
+
+/**
+ * Gracefully shuts down all child processes
+ */
+function shutdown() {
+ console.log('\nShutting down...');
+
+ for (const proc of childProcesses) {
+ if (proc && !proc.killed) {
+ proc.kill('SIGTERM');
+ }
+ }
+
+ // Force exit after a timeout if processes don't terminate
+ setTimeout(() => {
+ console.log('Force exiting...');
+ // eslint-disable-next-line no-undef
+ process.exit(0);
+ }, 3000);
+}
+
+/**
+ * Waits for the lib/components directory to exist (initial build complete)
+ */
+async function waitForBuild() {
+ const { existsSync } = await import('node:fs');
+ const componentsPath = path.resolve(rootDir, 'lib/components');
+
+ // If lib/components already exists, no need to wait
+ if (existsSync(componentsPath)) {
+ console.log('Build artifacts found, starting demo server...');
+ return;
+ }
+
+ console.log('Waiting for initial build to complete...');
+
+ return new Promise(resolve => {
+ const checkInterval = setInterval(() => {
+ if (existsSync(componentsPath)) {
+ clearInterval(checkInterval);
+ console.log('Build complete, starting demo server...');
+ resolve();
+ }
+ }, 1000);
+ });
+}
+
+/**
+ * Main entry point
+ */
+async function main() {
+ // Set up signal handlers for graceful shutdown
+ // eslint-disable-next-line no-undef
+ process.on('SIGINT', shutdown);
+ // eslint-disable-next-line no-undef
+ process.on('SIGTERM', shutdown);
+
+ console.log('Starting development environment...\n');
+
+ // Start the build watcher
+ console.log('Starting build watcher (gulp watch)...');
+ const watchProc = spawnProcess('npx', ['gulp', 'watch']);
+
+ watchProc.on('exit', code => {
+ if (code !== null && code !== 0) {
+ console.error(`Build watcher exited with code ${code}`);
+ shutdown();
+ }
+ });
+
+ // Wait for initial build to complete
+ await waitForBuild();
+
+ // Start the demo server
+ console.log('Starting demo server...');
+ const serverProc = spawnProcess('node', ['dev-server/server.mjs']);
+
+ serverProc.on('exit', code => {
+ if (code !== null && code !== 0) {
+ console.error(`Demo server exited with code ${code}`);
+ shutdown();
+ }
+ });
+}
+
+main().catch(err => {
+ console.error('Failed to start development environment:', err);
+ shutdown();
+});
diff --git a/dev-server/entry-client.tsx b/dev-server/entry-client.tsx
new file mode 100644
index 0000000000..75a59b0c62
--- /dev/null
+++ b/dev-server/entry-client.tsx
@@ -0,0 +1,246 @@
+// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
+// SPDX-License-Identifier: Apache-2.0
+
+import React from 'react';
+
+import App from './app';
+import IndexPage, { TreeItem } from './index-page';
+import { parseURLParams, URLParams } from './url-params';
+
+/**
+ * Handle legacy hash-based routes from the old dev server.
+ * Redirects URLs like /#/page-id to /page-id for backwards compatibility.
+ */
+function handleHashRedirect(): boolean {
+ const hash = window.location.hash;
+ if (hash && hash.startsWith('#/')) {
+ // Extract the path from the hash (e.g., "#/button/simple" -> "/button/simple")
+ const newPath = hash.slice(1); // Remove the leading "#"
+ // Preserve any query parameters from the original URL
+ const search = window.location.search;
+ window.location.replace(newPath + search);
+ return true; // Redirect in progress
+ }
+ return false;
+}
+
+// Global flags symbols (same as in pages/app/index.tsx and entry-server.tsx)
+const awsuiVisualRefreshFlag = Symbol.for('awsui-visual-refresh-flag');
+const awsuiGlobalFlagsSymbol = Symbol.for('awsui-global-flags');
+
+interface GlobalFlags {
+ appLayoutWidget?: boolean;
+ appLayoutToolbar?: boolean;
+}
+
+interface ExtendedWindow extends Window {
+ [awsuiVisualRefreshFlag]?: () => boolean;
+ [awsuiGlobalFlagsSymbol]?: GlobalFlags;
+}
+
+declare const window: ExtendedWindow;
+
+/**
+ * Set up global flags before hydration
+ * These flags must be set before any React rendering occurs
+ */
+function setupGlobalFlags(urlParams: URLParams): void {
+ // Set visual refresh flag
+ window[awsuiVisualRefreshFlag] = () => urlParams.visualRefresh;
+
+ // Set global flags for app layout
+ if (!window[awsuiGlobalFlagsSymbol]) {
+ window[awsuiGlobalFlagsSymbol] = {};
+ }
+ window[awsuiGlobalFlagsSymbol].appLayoutWidget = urlParams.appLayoutWidget;
+ window[awsuiGlobalFlagsSymbol].appLayoutToolbar = urlParams.appLayoutToolbar;
+
+ // Apply direction to document
+ document.documentElement.setAttribute('dir', urlParams.direction);
+}
+
+/**
+ * Parse the current URL to get page ID and parameters
+ */
+function parseCurrentURL(): { pageId: string | undefined; urlParams: URLParams } {
+ const url = new URL(window.location.href);
+
+ // Get page ID from pathname (remove leading slash)
+ let pathname = url.pathname;
+ // Remove trailing slash (except for root)
+ if (pathname !== '/' && pathname.endsWith('/')) {
+ pathname = pathname.slice(0, -1);
+ }
+ const pageId = pathname === '/' ? undefined : pathname.slice(1);
+
+ // Parse URL parameters
+ const urlParams = parseURLParams(url.searchParams);
+
+ return { pageId, urlParams };
+}
+
+/**
+ * Get the page tree from the embedded script tag (for index page hydration)
+ */
+function getPageTreeFromScript(): TreeItem | undefined {
+ const scriptElement = document.getElementById('ssr-page-tree');
+ if (scriptElement) {
+ try {
+ return JSON.parse(scriptElement.textContent || '');
+ } catch (e) {
+ console.warn('Failed to parse page tree from script:', e);
+ }
+ }
+ return undefined;
+}
+
+/**
+ * Pre-load all page modules using Vite's glob import
+ * This allows dynamic imports with nested paths (e.g., "app-layout-toolbar/with-content-layout")
+ */
+const pageModules = import.meta.glob('../pages/**/*.page.tsx');
+
+/**
+ * Load a page component dynamically
+ */
+async function loadPageComponent(pageId: string): Promise {
+ const pagePath = `../pages/${pageId}.page.tsx`;
+ const loader = pageModules[pagePath];
+
+ if (!loader) {
+ throw new Error(`Page not found: ${pageId}`);
+ }
+
+ try {
+ const pageModule = (await loader()) as { default: React.ComponentType };
+ return pageModule.default;
+ } catch (error) {
+ console.error(`Failed to load page "${pageId}":`, error);
+ throw error;
+ }
+}
+
+/**
+ * Error fallback component for pages that fail to load
+ */
+function PageLoadError({ pageId, error }: { pageId: string; error: Error }): React.ReactElement {
+ return (
+
+
Failed to Load Page
+
+ Could not load page: {pageId}
+
+
+ Error Details
+
+ {error.message}
+ {'\n\n'}
+ {error.stack}
+
+
+
+ ā Back to index
+
+
+ );
+}
+
+/**
+ * Check if the page was rendered with an SSR error (client-side only fallback)
+ */
+function wasSSRError(): boolean {
+ const errorMarker = document.querySelector('[data-ssr-error="true"]');
+ return errorMarker !== null;
+}
+
+/**
+ * Main hydration function
+ * Detects React version and uses appropriate hydration method
+ */
+async function hydrate(): Promise {
+ // Handle legacy hash-based routes first
+ if (handleHashRedirect()) {
+ return; // Redirect in progress, don't hydrate
+ }
+
+ const { pageId, urlParams } = parseCurrentURL();
+
+ // Set up global flags BEFORE any React rendering
+ setupGlobalFlags(urlParams);
+
+ // Get the app container
+ const container = document.getElementById('app');
+ if (!container) {
+ console.error('Could not find #app container for hydration');
+ return;
+ }
+
+ // Check if this page had an SSR error and needs client-side only rendering
+ const hadSSRError = wasSSRError();
+ if (hadSSRError && pageId) {
+ console.log(`Page "${pageId}" was not SSR'd, rendering client-side only`);
+ }
+
+ // Load the page component if needed
+ let PageComponent: React.ComponentType | null = null;
+ let loadError: Error | null = null;
+
+ if (pageId) {
+ try {
+ PageComponent = await loadPageComponent(pageId);
+ } catch (error) {
+ loadError = error instanceof Error ? error : new Error(String(error));
+ }
+ }
+
+ // Create the app element
+ const appElement = (
+
+ {loadError ? (
+
+ ) : PageComponent ? (
+
+ ) : (
+
+ )}
+
+ );
+
+ // Detect React version and hydrate accordingly
+ const reactVersion = React.version;
+ const isReact18 = reactVersion.startsWith('18');
+
+ console.log(`Hydrating with React ${reactVersion}${hadSSRError ? ' (client-side only)' : ''}`);
+
+ if (isReact18) {
+ // React 18: Use hydrateRoot for SSR'd pages, createRoot for client-only pages
+ // Use a variable to prevent Vite from statically analyzing this import
+ // (react-dom/client doesn't exist in React 16)
+ const reactDomClient = 'react-dom/client';
+ // eslint-disable-next-line no-unsanitized/method
+ const { hydrateRoot, createRoot } = (await import(/* @vite-ignore */ reactDomClient)) as any;
+ if (hadSSRError) {
+ // For pages that weren't SSR'd, use createRoot instead of hydrateRoot
+ // to avoid hydration mismatch warnings
+ createRoot(container).render(appElement);
+ } else {
+ hydrateRoot(container, appElement);
+ }
+ } else {
+ // React 16/17: Use ReactDOM.hydrate for SSR'd pages, render for client-only
+ const ReactDOM = (await import('react-dom')) as any;
+ if (hadSSRError) {
+ // For pages that weren't SSR'd, use render instead of hydrate
+ ReactDOM.render(appElement, container);
+ } else {
+ ReactDOM.hydrate(appElement, container);
+ }
+ }
+}
+
+// Start hydration when DOM is ready
+if (document.readyState === 'loading') {
+ document.addEventListener('DOMContentLoaded', hydrate);
+} else {
+ hydrate();
+}
diff --git a/dev-server/entry-server.tsx b/dev-server/entry-server.tsx
new file mode 100644
index 0000000000..6348fd0171
--- /dev/null
+++ b/dev-server/entry-server.tsx
@@ -0,0 +1,155 @@
+// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
+// SPDX-License-Identifier: Apache-2.0
+
+// IMPORTANT: Global flags are set by server.mjs BEFORE this module is loaded.
+// Do NOT set the visual refresh flag here - it must be set per-request.
+
+// These imports are safe - they don't touch the component library
+import type { TreeItem } from './index-page';
+import { parseURLParams } from './url-params';
+
+// Render options interface (accepts raw query params)
+export interface RenderOptions {
+ pageId?: string;
+ urlParams: Record;
+ // Page utilities passed from server.mjs (to avoid importing Node.js modules)
+ pageTree?: TreeItem;
+ pageExists?: boolean;
+ pageList?: string[];
+}
+
+// Render result interface
+export interface RenderResult {
+ html: string;
+ status: number;
+}
+
+// Main render function
+export async function render(options: RenderOptions): Promise {
+ const { pageId, urlParams: rawUrlParams, pageTree, pageExists, pageList } = options;
+
+ // Parse URL parameters with defaults
+ const urlParams = parseURLParams(rawUrlParams);
+
+ // Global flags are already set by server.mjs before this module is loaded
+ // Just clear the cached visual refresh state to ensure it's re-evaluated
+ const { clearVisualRefreshState } = await import('@cloudscape-design/component-toolkit/internal/testing');
+ clearVisualRefreshState();
+
+ // Now dynamically import React and components (after flags are set)
+ const React = await import('react');
+ const ReactDOMServer = await import('react-dom/server');
+ const { default: App } = await import('./app');
+ const { default: IndexPage } = await import('./index-page');
+
+ // 404 Not Found page component
+ function NotFoundPage({ pageId, pageList = [] }: { pageId: string; pageList?: string[] }): React.ReactElement {
+ return React.createElement(
+ 'div',
+ { style: { maxWidth: 800, margin: '0 auto', padding: '20px' } },
+ React.createElement('h1', { style: { color: '#d32f2f' } }, 'Page Not Found'),
+ React.createElement('p', null, 'The page ', React.createElement('code', null, pageId), ' does not exist.'),
+ React.createElement('p', null, React.createElement('a', { href: '/' }, 'ā Back to index')),
+ React.createElement(
+ 'details',
+ null,
+ React.createElement('summary', null, `Available pages (${pageList.length})`),
+ React.createElement(
+ 'ul',
+ null,
+ pageList
+ .slice(0, 50)
+ .map((page: string) =>
+ React.createElement('li', { key: page }, React.createElement('a', { href: `/${page}` }, page))
+ ),
+ pageList.length > 50 && React.createElement('li', null, `... and ${pageList.length - 50} more`)
+ )
+ )
+ );
+ }
+
+ // Handle index page
+ if (!pageId) {
+ // Embed the page tree as JSON for client-side hydration
+ const pageTreeScript = pageTree
+ ? ``
+ : '';
+ const appHtml = ReactDOMServer.renderToString(
+ React.createElement(
+ App,
+ { pageId: undefined, urlParams, isServer: true },
+ React.createElement(IndexPage, { pageTree })
+ )
+ );
+ return { html: pageTreeScript + appHtml, status: 200 };
+ }
+
+ // Check if page exists
+ if (pageExists === false) {
+ const html = ReactDOMServer.renderToString(
+ React.createElement(
+ App,
+ { pageId, urlParams, isServer: true },
+ React.createElement(NotFoundPage, { pageId, pageList })
+ )
+ );
+ return { html, status: 404 };
+ }
+
+ // Try to dynamically import and render the page
+ try {
+ // Dynamic import of the page module
+ const pages = import.meta.glob('../pages/**/*.page.tsx');
+ const pagePath = `../pages/${pageId}.page.tsx`;
+
+ if (!pages[pagePath]) {
+ throw new Error(`Page module not found: ${pagePath}`);
+ }
+
+ const pageModule = (await pages[pagePath]()) as { default: React.ComponentType };
+ const PageComponent = pageModule.default;
+
+ if (!PageComponent) {
+ throw new Error(`Page module "${pageId}" does not have a default export`);
+ }
+
+ const html = ReactDOMServer.renderToString(
+ React.createElement(App, { pageId, urlParams, isServer: true }, React.createElement(PageComponent))
+ );
+ return { html, status: 200 };
+ } catch (error) {
+ // If the page can't be rendered, return a placeholder for client-side rendering
+ const errorMessage = error instanceof Error ? error.message : String(error);
+ const errorStack = error instanceof Error ? error.stack : undefined;
+
+ console.warn(`ā ļø SSR Warning for page "${pageId}": Page will be rendered client-side only`);
+ console.warn(` Reason: ${errorMessage}`);
+ if (errorStack) {
+ console.warn(` Stack: ${errorStack.split('\n').slice(1, 4).join('\n ')}`);
+ }
+
+ const errorPlaceholder = React.createElement(
+ 'div',
+ { 'data-ssr-error': 'true', 'data-page-id': pageId, 'data-error-message': errorMessage },
+ React.createElement(
+ 'div',
+ { style: { padding: '20px', textAlign: 'center' } },
+ React.createElement('p', { style: { color: '#666' } }, `Loading ${pageId}...`),
+ React.createElement(
+ 'p',
+ { style: { fontSize: '12px', color: '#999' } },
+ '(This page is rendered client-side only)'
+ )
+ )
+ );
+
+ const html = ReactDOMServer.renderToString(
+ React.createElement(App, { pageId, urlParams, isServer: true }, errorPlaceholder)
+ );
+ return { html, status: 200 };
+ }
+}
+
+// Re-export URL params utilities
+export { parseURLParams, formatURLParams, defaultURLParams } from './url-params';
+export type { URLParams } from './url-params';
diff --git a/dev-server/error-boundary.tsx b/dev-server/error-boundary.tsx
new file mode 100644
index 0000000000..044349f976
--- /dev/null
+++ b/dev-server/error-boundary.tsx
@@ -0,0 +1,57 @@
+// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
+// SPDX-License-Identifier: Apache-2.0
+
+import React, { Component, ErrorInfo, ReactNode } from 'react';
+
+interface ErrorBoundaryProps {
+ children: ReactNode;
+ pageId?: string;
+}
+
+interface ErrorBoundaryState {
+ hasError: boolean;
+ errorMessage: string;
+}
+
+/**
+ * Error Boundary component for catching client-side rendering errors
+ *
+ * This component wraps page content and catches any errors that occur during
+ * React rendering on the client side. When an error is caught, it displays
+ * the error message in red text, matching the behavior of the non-SSR demo server.
+ *
+ * Note: Error boundaries only catch errors in their child component tree.
+ * They do not catch errors in event handlers, async code, or server-side rendering.
+ */
+export default class ErrorBoundary extends Component {
+ constructor(props: ErrorBoundaryProps) {
+ super(props);
+ this.state = {
+ hasError: false,
+ errorMessage: '',
+ };
+ }
+
+ static getDerivedStateFromError(error: Error): ErrorBoundaryState {
+ // Update state so the next render will show the fallback UI
+ return {
+ hasError: true,
+ errorMessage: error.stack || error.message,
+ };
+ }
+
+ componentDidCatch(error: Error, errorInfo: ErrorInfo): void {
+ // Log the error for debugging
+ console.error('ErrorBoundary caught an error:', error);
+ console.error('Component stack:', errorInfo.componentStack);
+ }
+
+ render(): ReactNode {
+ if (this.state.hasError) {
+ // Match the non-SSR demo server's error display: red text with the error
+ return {this.state.errorMessage} ;
+ }
+
+ return this.props.children;
+ }
+}
diff --git a/dev-server/index-page.tsx b/dev-server/index-page.tsx
new file mode 100644
index 0000000000..71b705c7b5
--- /dev/null
+++ b/dev-server/index-page.tsx
@@ -0,0 +1,70 @@
+// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
+// SPDX-License-Identifier: Apache-2.0
+
+import React from 'react';
+
+/**
+ * Tree item interface for the page tree structure
+ */
+export interface TreeItem {
+ name: string;
+ href?: string;
+ items: TreeItem[];
+}
+
+/**
+ * Props for IndexPage component
+ */
+export interface IndexPageProps {
+ pageTree?: TreeItem;
+}
+
+/**
+ * Renders a tree item with its children
+ */
+function TreeItemView({ item }: { item: TreeItem }) {
+ return (
+
+ {item.href ? {item.name} : {item.name} }
+ {item.items.length > 0 && (
+
+ {item.items.map(child => (
+
+ ))}
+
+ )}
+
+ );
+}
+
+/**
+ * Index page component that lists all available demo pages
+ * Similar to pages/app/components/index-page.tsx but for SSR
+ *
+ * The pageTree prop is passed from the server to avoid importing
+ * Node.js-only modules (page-loader.js) on the client side.
+ */
+export default function IndexPage({ pageTree }: IndexPageProps) {
+ // If no pageTree is provided (client-side hydration), show a loading message
+ // The server will always provide the pageTree
+ if (!pageTree) {
+ return (
+
+
Cloudscape Demo Pages
+
Loading page list...
+
+ );
+ }
+
+ return (
+
+
Cloudscape Demo Pages
+
Select a page:
+
+ {pageTree.items.map(item => (
+
+ ))}
+
+
+ );
+}
diff --git a/dev-server/index.html b/dev-server/index.html
new file mode 100644
index 0000000000..fbdfbca21f
--- /dev/null
+++ b/dev-server/index.html
@@ -0,0 +1,16 @@
+
+
+
+
+
+ Cloudscape Demo Pages
+
+
+
+
+
+
+
+
diff --git a/dev-server/page-loader.d.ts b/dev-server/page-loader.d.ts
new file mode 100644
index 0000000000..33f18ea3ba
--- /dev/null
+++ b/dev-server/page-loader.d.ts
@@ -0,0 +1,37 @@
+// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
+// SPDX-License-Identifier: Apache-2.0
+
+/**
+ * Tree item interface for the page tree structure
+ */
+export interface TreeItem {
+ name: string;
+ href?: string;
+ items: TreeItem[];
+}
+
+/**
+ * Scans for all demo pages matching the pattern pages/*.page.tsx
+ * and returns a list of page IDs (e.g., "alert/simple", "app-layout/with-drawers")
+ */
+export function getPageList(): string[];
+
+/**
+ * Checks if a page exists by its page ID
+ */
+export function pageExists(pageId: string): boolean;
+
+/**
+ * Gets the file path for a page ID
+ */
+export function getPagePath(pageId: string): string;
+
+/**
+ * Clears the cached page list (useful for development when pages are added/removed)
+ */
+export function clearPageCache(): void;
+
+/**
+ * Creates a tree structure from the page list for display in the index page
+ */
+export function createPagesTree(pages: string[]): TreeItem;
diff --git a/dev-server/page-loader.js b/dev-server/page-loader.js
new file mode 100644
index 0000000000..140564abee
--- /dev/null
+++ b/dev-server/page-loader.js
@@ -0,0 +1,72 @@
+// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
+// SPDX-License-Identifier: Apache-2.0
+
+import { glob } from 'glob';
+import path from 'node:path';
+import { fileURLToPath } from 'node:url';
+
+const __dirname = path.dirname(fileURLToPath(import.meta.url));
+const rootDir = path.resolve(__dirname, '..');
+const pagesDir = path.resolve(rootDir, 'pages');
+
+// Cache for page list to avoid repeated filesystem scans
+let cachedPageList = null;
+
+// Scans for all demo pages matching the pattern pages/**/*.page.tsx
+// and returns a list of page IDs (e.g., "alert/simple", "app-layout/with-drawers")
+export function getPageList() {
+ if (cachedPageList !== null) {
+ return cachedPageList;
+ }
+
+ const pageFiles = glob.sync('**/*.page.tsx', {
+ cwd: pagesDir,
+ ignore: ['app/**'], // Ignore the app directory itself
+ });
+
+ cachedPageList = pageFiles.map(file => file.replace(/\.page\.tsx$/, ''));
+ return cachedPageList;
+}
+
+// Checks if a page exists by its page ID
+export function pageExists(pageId) {
+ const pageList = getPageList();
+ return pageList.includes(pageId);
+}
+
+// Gets the file path for a page ID
+export function getPagePath(pageId) {
+ return path.resolve(pagesDir, `${pageId}.page.tsx`);
+}
+
+// Clears the cached page list (useful for development when pages are added/removed)
+export function clearPageCache() {
+ cachedPageList = null;
+}
+
+// Creates a tree structure from the page list for display in the index page
+export function createPagesTree(pages) {
+ const tree = { name: '.', items: [] };
+
+ function putInTree(segments, node, item) {
+ if (segments.length === 0) {
+ node.href = item;
+ } else {
+ let match = node.items.find(child => child.name === segments[0]);
+ if (!match) {
+ match = { name: segments[0], items: [] };
+ node.items.push(match);
+ }
+ putInTree(segments.slice(1), match, item);
+ // Make directories display above files
+ node.items.sort((a, b) => Math.min(b.items.length, 1) - Math.min(a.items.length, 1));
+ }
+ }
+
+ for (const page of pages) {
+ const segments = page.split('/');
+ putInTree(segments, tree, page);
+ }
+
+ return tree;
+}
diff --git a/dev-server/server.mjs b/dev-server/server.mjs
new file mode 100644
index 0000000000..ebdbde42e4
--- /dev/null
+++ b/dev-server/server.mjs
@@ -0,0 +1,158 @@
+// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
+// SPDX-License-Identifier: Apache-2.0
+
+import express from 'express';
+import fs from 'node:fs';
+import path from 'node:path';
+import { fileURLToPath } from 'node:url';
+import { createServer as createViteServer } from 'vite';
+
+// Import page-loader directly (Node.js module, not through Vite)
+import { createPagesTree, getPageList, pageExists } from './page-loader.js';
+
+const __dirname = path.dirname(fileURLToPath(import.meta.url));
+const rootDir = path.resolve(__dirname, '..');
+
+// eslint-disable-next-line no-undef
+const port = process.env.PORT || 8080;
+
+async function createServer() {
+ const app = express();
+
+ // Create Vite server in middleware mode
+ const vite = await createViteServer({
+ configFile: path.resolve(__dirname, 'vite.config.js'),
+ server: { middlewareMode: true },
+ appType: 'custom',
+ });
+
+ // Use Vite's connect instance as middleware
+ app.use(vite.middlewares);
+
+ // Serve static files from lib directory
+ app.use('/lib', express.static(path.resolve(rootDir, 'lib')));
+
+ // Handle all routes - use wildcard for Express 4.x
+ app.use('*', async (req, res) => {
+ const url = req.originalUrl;
+
+ try {
+ // Read the HTML template
+ let template = fs.readFileSync(path.resolve(__dirname, 'index.html'), 'utf-8');
+
+ // Apply Vite HTML transforms (handles HMR injection, etc.)
+ template = await vite.transformIndexHtml(url, template);
+
+ // Parse URL to get page ID and query parameters BEFORE loading modules
+ // Handle both /page and /page/ formats
+ let urlPath = url.split('?')[0];
+ // Remove trailing slash (except for root)
+ if (urlPath !== '/' && urlPath.endsWith('/')) {
+ urlPath = urlPath.slice(0, -1);
+ }
+ const pageId = urlPath === '/' ? undefined : urlPath.slice(1);
+ const urlParams = Object.fromEntries(new URL(url, `http://localhost:${port}`).searchParams);
+
+ // Clear the visual refresh state BEFORE loading any component modules
+ // This ensures the visual refresh flag is re-evaluated per request
+ const { clearVisualRefreshState } = await vite.ssrLoadModule(
+ '@cloudscape-design/component-toolkit/internal/testing'
+ );
+ clearVisualRefreshState();
+
+ // Set the visual refresh flag based on URL params BEFORE loading entry-server
+ const visualRefresh = urlParams.visualRefresh !== 'false';
+ globalThis[Symbol.for('awsui-visual-refresh-flag')] = () => visualRefresh;
+
+ // Set up other global flags
+ const globalFlagsSymbol = Symbol.for('awsui-global-flags');
+ if (!globalThis[globalFlagsSymbol]) {
+ globalThis[globalFlagsSymbol] = {};
+ }
+ globalThis[globalFlagsSymbol].appLayoutWidget = urlParams.appLayoutWidget === 'true';
+ globalThis[globalFlagsSymbol].appLayoutToolbar = urlParams.appLayoutToolbar === 'true';
+
+ // Load the server entry module (path relative to Vite root which is dev-server/)
+ const { render } = await vite.ssrLoadModule('/entry-server.tsx');
+
+ // Collect CSS styles from built components
+ let styles = '';
+ try {
+ const { collectStyles: getStyles } = await vite.ssrLoadModule('/collect-styles.mjs');
+ styles = getStyles();
+ } catch (e) {
+ console.warn('Warning: Could not collect styles:', e.message);
+ }
+
+ // Get page utilities (using Node.js module directly, not through Vite)
+ const pageList = getPageList();
+ const pageTree = createPagesTree(pageList);
+ const pageExistsResult = pageId ? pageExists(pageId) : true;
+
+ // Render the app HTML
+ const { html: appHtml, status } = await render({
+ pageId,
+ urlParams,
+ pageTree,
+ pageExists: pageExistsResult,
+ pageList,
+ });
+
+ // Inject styles and rendered HTML into template
+ // Build body classes based on URL params for SSR
+ const bodyClasses = [];
+ if (visualRefresh) {
+ bodyClasses.push('awsui-visual-refresh');
+ }
+ if (urlParams.mode === 'dark') {
+ bodyClasses.push('awsui-dark-mode');
+ }
+ if (urlParams.density === 'compact') {
+ bodyClasses.push('awsui-compact-mode');
+ }
+ if (urlParams.motionDisabled === 'true') {
+ bodyClasses.push('awsui-motion-disabled');
+ }
+ const bodyClass = bodyClasses.join(' ');
+
+ const finalHtml = template
+ .replace('', styles ? `` : '')
+ .replace('', bodyClass)
+ .replace('', appHtml);
+
+ // Send the response
+ res.status(status).set({ 'Content-Type': 'text/html' }).end(finalHtml);
+ } catch (e) {
+ // Fix stack trace for Vite
+ vite.ssrFixStacktrace(e);
+
+ console.error('Render Error:', e.stack);
+
+ // Return error page
+ res.status(500).set({ 'Content-Type': 'text/html' }).end(`
+
+
+
+ Render Error
+
+
+
+ Rendering Error
+ ${e.message}
+ ${e.stack}
+
+
+ `);
+ }
+ });
+
+ app.listen(port, () => {
+ console.log(`Demo server running at http://localhost:${port}`);
+ });
+}
+
+createServer();
diff --git a/dev-server/ssr-utils.ts b/dev-server/ssr-utils.ts
new file mode 100644
index 0000000000..b4d840b5fc
--- /dev/null
+++ b/dev-server/ssr-utils.ts
@@ -0,0 +1,45 @@
+// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
+// SPDX-License-Identifier: Apache-2.0
+
+/**
+ * Utility functions for SSR compatibility in demo pages
+ */
+
+/**
+ * Check if code is running on the server (SSR) or client
+ */
+export const isServer = typeof window === 'undefined';
+
+/**
+ * Check if code is running on the client (browser)
+ */
+export const isClient = typeof window !== 'undefined';
+
+/**
+ * Safely access window object, returns undefined on server
+ */
+export function getWindow(): Window | undefined {
+ return isClient ? window : undefined;
+}
+
+/**
+ * Safely access document object, returns undefined on server
+ */
+export function getDocument(): Document | undefined {
+ return isClient ? document : undefined;
+}
+
+/**
+ * Safely access navigator object, returns undefined on server
+ */
+export function getNavigator(): Navigator | undefined {
+ return isClient ? navigator : undefined;
+}
+
+/**
+ * Execute a function only on the client side
+ * Returns undefined on the server
+ */
+export function clientOnly(fn: () => T): T | undefined {
+ return isClient ? fn() : undefined;
+}
diff --git a/dev-server/tsconfig.json b/dev-server/tsconfig.json
new file mode 100644
index 0000000000..d1ceb792e6
--- /dev/null
+++ b/dev-server/tsconfig.json
@@ -0,0 +1,26 @@
+{
+ "compilerOptions": {
+ "lib": ["ES2020", "DOM"],
+ "target": "ES2019",
+ "declaration": false,
+ "declarationMap": false,
+ "rootDir": ".",
+ "incremental": true,
+ "jsx": "react",
+ "module": "esnext",
+ "moduleResolution": "node",
+ "esModuleInterop": true,
+ "importHelpers": true,
+ "strict": true,
+ "skipLibCheck": true,
+ "allowJs": true,
+ "checkJs": false,
+ "paths": {
+ "~components": ["../lib/components"],
+ "~components/*": ["../lib/components/*"],
+ "~design-tokens": ["../lib/design-tokens"]
+ },
+ "types": ["node", "vite/client"]
+ },
+ "include": ["**/*.ts", "**/*.tsx", "**/*.js", "**/*.mjs"]
+}
diff --git a/dev-server/url-params.ts b/dev-server/url-params.ts
new file mode 100644
index 0000000000..d187c61f00
--- /dev/null
+++ b/dev-server/url-params.ts
@@ -0,0 +1,90 @@
+// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
+// SPDX-License-Identifier: Apache-2.0
+
+/**
+ * URL parameters interface for configuring demo page rendering
+ */
+export interface URLParams {
+ visualRefresh: boolean;
+ density: 'comfortable' | 'compact';
+ motionDisabled: boolean;
+ direction: 'ltr' | 'rtl';
+ mode: 'light' | 'dark';
+ appLayoutWidget: boolean;
+ appLayoutToolbar: boolean;
+}
+
+/**
+ * Default values for URL parameters
+ * Note: visualRefresh defaults to true to match the behavior of the non-SSR dev server
+ * when THEME === 'default' (which is the case for the built components)
+ */
+export const defaultURLParams: URLParams = {
+ visualRefresh: true,
+ density: 'comfortable',
+ motionDisabled: false,
+ direction: 'ltr',
+ mode: 'light',
+ appLayoutWidget: false,
+ appLayoutToolbar: false,
+};
+
+/**
+ * Parse a boolean value from a string or boolean
+ */
+function parseBoolean(value: string | boolean | undefined, defaultValue: boolean): boolean {
+ if (value === undefined) {
+ return defaultValue;
+ }
+ if (typeof value === 'boolean') {
+ return value;
+ }
+ return value === 'true';
+}
+
+/**
+ * Parse URL query parameters into URLParams object
+ * @param query - Query string or URLSearchParams or plain object
+ * @returns Parsed URL parameters with defaults applied
+ */
+export function parseURLParams(query: string | URLSearchParams | Record): URLParams {
+ let params: Record;
+
+ if (typeof query === 'string') {
+ // Handle query string (with or without leading ?)
+ const searchParams = new URLSearchParams(query.startsWith('?') ? query.slice(1) : query);
+ params = Object.fromEntries(searchParams.entries());
+ } else if (query instanceof URLSearchParams) {
+ params = Object.fromEntries(query.entries());
+ } else {
+ params = query;
+ }
+
+ return {
+ visualRefresh: parseBoolean(params.visualRefresh, defaultURLParams.visualRefresh),
+ density: params.density === 'compact' ? 'compact' : defaultURLParams.density,
+ motionDisabled: parseBoolean(params.motionDisabled, defaultURLParams.motionDisabled),
+ direction: params.direction === 'rtl' ? 'rtl' : defaultURLParams.direction,
+ mode: params.mode === 'dark' ? 'dark' : defaultURLParams.mode,
+ appLayoutWidget: parseBoolean(params.appLayoutWidget, defaultURLParams.appLayoutWidget),
+ appLayoutToolbar: parseBoolean(params.appLayoutToolbar, defaultURLParams.appLayoutToolbar),
+ };
+}
+
+/**
+ * Format URL parameters back to a query string
+ * Only includes non-default values
+ */
+export function formatURLParams(params: Partial): string {
+ const query = new URLSearchParams();
+
+ for (const [key, value] of Object.entries(params)) {
+ const defaultValue = defaultURLParams[key as keyof URLParams];
+ if (value !== defaultValue && value !== undefined) {
+ query.set(key, String(value));
+ }
+ }
+
+ const queryString = query.toString();
+ return queryString ? `?${queryString}` : '';
+}
diff --git a/dev-server/vite.config.js b/dev-server/vite.config.js
new file mode 100644
index 0000000000..287b0b5299
--- /dev/null
+++ b/dev-server/vite.config.js
@@ -0,0 +1,347 @@
+// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
+// SPDX-License-Identifier: Apache-2.0
+
+/**
+ * Vite configuration for the demo development server
+ *
+ * React 18 Support:
+ * When REACT_VERSION=18 is set, this config uses a custom plugin to rewrite
+ * react/react-dom imports to react18/react-dom18 packages. The packages are
+ * externalized so Node.js loads them directly (handling CommonJS properly).
+ *
+ * We use Vite 5.x because:
+ * - Vite 5's legacy.proxySsrExternalModules option provides CommonJS interop
+ * - This allows the react18 package (CommonJS-only) to work correctly in SSR
+ * - Vite 6+ removed this option and has stricter ESM requirements
+ */
+
+import react from '@vitejs/plugin-react';
+import { createRequire } from 'module';
+import path from 'path';
+import { fileURLToPath } from 'url';
+import { defineConfig } from 'vite';
+
+const __dirname = path.dirname(fileURLToPath(import.meta.url));
+const rootDir = path.resolve(__dirname, '..');
+const require = createRequire(import.meta.url);
+
+// eslint-disable-next-line no-undef
+const react18 = process.env.REACT_VERSION === '18';
+
+if (react18) {
+ console.log('\nš React 18 mode enabled - using react18/react-dom18 packages\n');
+}
+
+/**
+ * Plugin to handle React 18 aliasing
+ * - For SSR: resolve to react18/react-dom18 and mark as external
+ * - For client: resolve to the actual file paths for bundling
+ */
+function react18Plugin() {
+ // Pre-resolve the package entry points
+ let react18Entry = null;
+ let reactDom18Entry = null;
+
+ if (react18) {
+ try {
+ react18Entry = require.resolve('react18');
+ reactDom18Entry = require.resolve('react-dom18');
+ } catch (e) {
+ console.error('Failed to resolve react18/react-dom18 packages:', e.message);
+ }
+ }
+
+ return {
+ name: 'react18-resolver',
+ enforce: 'pre',
+ resolveId(source, importer, options) {
+ if (!react18) {
+ return null;
+ }
+
+ const isSSR = options?.ssr;
+
+ // Handle react imports
+ if (source === 'react') {
+ if (isSSR) {
+ return { id: 'react18', external: true };
+ } else {
+ return react18Entry;
+ }
+ }
+ if (source.startsWith('react/')) {
+ const subpath = source.slice('react/'.length);
+ if (isSSR) {
+ return { id: `react18/${subpath}`, external: true };
+ } else {
+ // Resolve the subpath relative to react18 package
+ try {
+ return require.resolve(`react18/${subpath}`);
+ } catch {
+ return null; // Let Vite handle it
+ }
+ }
+ }
+
+ // Handle react-dom imports
+ if (source === 'react-dom') {
+ if (isSSR) {
+ return { id: 'react-dom18', external: true };
+ } else {
+ return reactDom18Entry;
+ }
+ }
+ if (source.startsWith('react-dom/')) {
+ const subpath = source.slice('react-dom/'.length);
+ if (isSSR) {
+ return { id: `react-dom18/${subpath}`, external: true };
+ } else {
+ // For react-dom/client, resolve to the actual react-dom18 package's client entry
+ // require.resolve doesn't work with package subpath exports, so we resolve manually
+ if (subpath === 'client') {
+ const reactDom18Dir = path.dirname(reactDom18Entry);
+ return path.join(reactDom18Dir, 'client.js');
+ }
+ try {
+ return require.resolve(`react-dom18/${subpath}`);
+ } catch {
+ return null; // Let Vite handle it
+ }
+ }
+ }
+
+ // Handle already-rewritten imports (from JSX transform with jsxImportSource)
+ if (source === 'react18') {
+ if (isSSR) {
+ return { id: source, external: true };
+ } else {
+ return react18Entry;
+ }
+ }
+ if (source.startsWith('react18/')) {
+ const subpath = source.slice('react18/'.length);
+ if (isSSR) {
+ return { id: source, external: true };
+ } else {
+ try {
+ return require.resolve(`react18/${subpath}`);
+ } catch {
+ return null;
+ }
+ }
+ }
+
+ if (source === 'react-dom18') {
+ if (isSSR) {
+ return { id: source, external: true };
+ } else {
+ return reactDom18Entry;
+ }
+ }
+ if (source.startsWith('react-dom18/')) {
+ const subpath = source.slice('react-dom18/'.length);
+ if (isSSR) {
+ return { id: source, external: true };
+ } else {
+ // For react-dom18/client, resolve to the actual package's client entry
+ if (subpath === 'client') {
+ const reactDom18Dir = path.dirname(reactDom18Entry);
+ return path.join(reactDom18Dir, 'client.js');
+ }
+ try {
+ return require.resolve(`react-dom18/${subpath}`);
+ } catch {
+ return null;
+ }
+ }
+ }
+
+ return null;
+ },
+ };
+}
+
+/**
+ * Plugin to skip CSS imports from lib/components since they're already
+ * collected and injected during SSR via collect-styles.mjs.
+ *
+ * The styles.css.js files import .scoped.css - we strip that import
+ * since the CSS is already in the SSR response.
+ */
+function skipLibComponentsCssPlugin() {
+ return {
+ name: 'skip-lib-components-css',
+ enforce: 'pre',
+
+ resolveId(source, importer) {
+ // Skip .scoped.css files from lib/components - they're already in the SSR styles
+ if (source.endsWith('.scoped.css') && importer && importer.includes('lib/components')) {
+ return { id: `\0skip-css:${source}`, external: false };
+ }
+ if (source.endsWith('.scoped.css') && source.includes('lib/components')) {
+ return { id: `\0skip-css:${source}`, external: false };
+ }
+ return null;
+ },
+
+ load(id) {
+ // Return empty module for skipped CSS
+ if (id.startsWith('\0skip-css:')) {
+ return 'export default {}';
+ }
+ return null;
+ },
+
+ transform(code, id) {
+ // For styles.css.js files in lib/components, remove the CSS import
+ if (id.includes('lib/components') && id.endsWith('styles.css.js')) {
+ return code.replace(/^\s*import\s+['"]\.\/styles\.scoped\.css['"];\s*$/m, '');
+ }
+ return null;
+ },
+ };
+}
+
+/**
+ * Plugin to treat all .scss files in pages/ as CSS modules
+ * Vite only treats .module.scss as CSS modules by default.
+ * This plugin creates virtual .module.scss files that load the real .scss content.
+ */
+function pagesScssModulesPlugin() {
+ const virtualToReal = new Map();
+
+ return {
+ name: 'pages-scss-modules',
+ enforce: 'pre',
+
+ async resolveId(source, importer, options) {
+ // Only handle .scss files that aren't already .module.scss
+ if (!source.endsWith('.scss') || source.endsWith('.module.scss')) {
+ return null;
+ }
+
+ // Only handle relative imports from within pages/
+ if (!importer || !importer.includes('/pages/')) {
+ return null;
+ }
+
+ if (!source.startsWith('./') && !source.startsWith('../')) {
+ return null;
+ }
+
+ // Resolve the actual path
+ const resolved = await this.resolve(source, importer, { ...options, skipSelf: true });
+ if (!resolved) {
+ return null;
+ }
+
+ // Create a virtual .module.scss path
+ const virtualPath = resolved.id.replace(/\.scss$/, '.virtual.module.scss');
+ virtualToReal.set(virtualPath, resolved.id);
+
+ return virtualPath;
+ },
+
+ async load(id) {
+ // Handle our virtual .module.scss files
+ const realPath = virtualToReal.get(id);
+ if (realPath) {
+ const fs = await import('fs/promises');
+ const content = await fs.readFile(realPath, 'utf-8');
+ return content;
+ }
+ return null;
+ },
+ };
+}
+
+// Build resolve aliases (non-React)
+const aliases = [
+ // Map @cloudscape-design/components to built lib/components
+ { find: '@cloudscape-design/components', replacement: path.resolve(rootDir, 'lib/components') },
+ { find: '~components', replacement: path.resolve(rootDir, 'lib/components') },
+ { find: '~design-tokens', replacement: path.resolve(rootDir, 'lib/design-tokens') },
+ { find: '@cloudscape-design/design-tokens', replacement: path.resolve(rootDir, 'lib/design-tokens') },
+];
+
+export default defineConfig({
+ root: path.resolve(__dirname),
+
+ plugins: [
+ skipLibComponentsCssPlugin(),
+ pagesScssModulesPlugin(),
+ react18Plugin(),
+ react({
+ // Configure JSX runtime for React 18 mode
+ jsxImportSource: react18 ? 'react18' : 'react',
+ }),
+ ],
+
+ resolve: {
+ alias: aliases,
+ extensions: ['.mjs', '.ts', '.tsx', '.js', '.jsx'],
+ },
+
+ // SSR configuration
+ ssr: {
+ // Target Node.js for SSR
+ target: 'node',
+ // Include these packages in the SSR bundle (don't externalize them)
+ noExternal: [
+ '@cloudscape-design/components',
+ '@cloudscape-design/design-tokens',
+ '@cloudscape-design/collection-hooks',
+ '@cloudscape-design/component-toolkit',
+ '@cloudscape-design/theming-runtime',
+ '@cloudscape-design/global-styles',
+ ],
+ // Externalize Node.js modules
+ external: ['glob', 'fs', 'path', 'node:fs', 'node:path', 'node:url'],
+ },
+
+ // Legacy options for better CommonJS interop in SSR
+ legacy: {
+ proxySsrExternalModules: true,
+ },
+
+ // Optimize dependencies for faster dev startup
+ optimizeDeps: {
+ include: react18
+ ? ['react18', 'react-dom18', 'react-router-dom', 'prop-types', 'react-is', 'react-keyed-flatten-children']
+ : ['react', 'react-dom', 'react-router-dom', 'prop-types', 'react-is', 'react-keyed-flatten-children'],
+ // Don't try to pre-bundle local lib/components - it has TypeScript type exports
+ // that esbuild can't handle, and we want HMR for development anyway
+ exclude: ['@cloudscape-design/components', '~components'],
+ // Don't scan pages for dependencies - they import from local lib
+ entries: [],
+ },
+
+ // CSS handling
+ css: {
+ modules: {
+ localsConvention: 'camelCase',
+ },
+ preprocessorOptions: {
+ scss: {
+ // Add the design-tokens path for @use '~design-tokens'
+ includePaths: [path.resolve(rootDir, 'lib')],
+ },
+ },
+ },
+
+ // Build configuration (for potential production builds)
+ build: {
+ outDir: path.resolve(rootDir, 'dist/ssr'),
+ emptyOutDir: true,
+ },
+
+ // Server configuration
+ server: {
+ port: 3000,
+ strictPort: false,
+ fs: {
+ // Allow serving files from the entire project
+ allow: [rootDir],
+ },
+ },
+});
diff --git a/package-lock.json b/package-lock.json
index 19b7fbdb38..83daa27b31 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -57,6 +57,7 @@
"@types/react-test-renderer": "^16.9.12",
"@types/react-transition-group": "^4.4.4",
"@types/webpack-env": "^1.16.3",
+ "@vitejs/plugin-react": "^4.3.4",
"axe-core": "^4.7.2",
"babel-jest": "^29.7.0",
"change-case": "^4.1.2",
@@ -77,6 +78,7 @@
"eslint-plugin-simple-import-sort": "^12.1.1",
"eslint-plugin-unicorn": "^60.0.0",
"execa": "^4.1.0",
+ "express": "^4.21.2",
"fs-extra": "^11.2.0",
"glob": "^11.1.0",
"globals": "^16.1.0",
@@ -121,6 +123,7 @@
"types-react18": "npm:@types/react@^18.3.24",
"typescript": "^5.9.2",
"typescript-eslint": "^8.44.0",
+ "vite": "^5.4.21",
"wait-on": "^8.0.2",
"webpack": "^5.94.0",
"webpack-cli": "^5.1.4",
@@ -143,18 +146,6 @@
"dev": true,
"license": "MIT"
},
- "node_modules/@ampproject/remapping": {
- "version": "2.3.0",
- "dev": true,
- "license": "Apache-2.0",
- "dependencies": {
- "@jridgewell/gen-mapping": "^0.3.5",
- "@jridgewell/trace-mapping": "^0.3.24"
- },
- "engines": {
- "node": ">=6.0.0"
- }
- },
"node_modules/@aws-crypto/sha256-browser": {
"version": "5.2.0",
"dev": true,
@@ -766,20 +757,22 @@
}
},
"node_modules/@babel/core": {
- "version": "7.27.4",
+ "version": "7.28.5",
+ "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.28.5.tgz",
+ "integrity": "sha512-e7jT4DxYvIDLk1ZHmU/m/mB19rex9sv0c2ftBtjSBv+kVM/902eh0fINUzD7UwLLNR+jU585GxUJ8/EBfAM5fw==",
"dev": true,
"license": "MIT",
"dependencies": {
- "@ampproject/remapping": "^2.2.0",
"@babel/code-frame": "^7.27.1",
- "@babel/generator": "^7.27.3",
+ "@babel/generator": "^7.28.5",
"@babel/helper-compilation-targets": "^7.27.2",
- "@babel/helper-module-transforms": "^7.27.3",
- "@babel/helpers": "^7.27.4",
- "@babel/parser": "^7.27.4",
+ "@babel/helper-module-transforms": "^7.28.3",
+ "@babel/helpers": "^7.28.4",
+ "@babel/parser": "^7.28.5",
"@babel/template": "^7.27.2",
- "@babel/traverse": "^7.27.4",
- "@babel/types": "^7.27.3",
+ "@babel/traverse": "^7.28.5",
+ "@babel/types": "^7.28.5",
+ "@jridgewell/remapping": "^2.3.5",
"convert-source-map": "^2.0.0",
"debug": "^4.1.0",
"gensync": "^1.0.0-beta.2",
@@ -795,14 +788,16 @@
}
},
"node_modules/@babel/generator": {
- "version": "7.27.5",
+ "version": "7.28.5",
+ "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.28.5.tgz",
+ "integrity": "sha512-3EwLFhZ38J4VyIP6WNtt2kUdW9dokXA9Cr4IVIFHuCpZ3H8/YFOl5JjZHisrn1fATPBmKKqXzDFvh9fUwHz6CQ==",
"dev": true,
"license": "MIT",
"dependencies": {
- "@babel/parser": "^7.27.5",
- "@babel/types": "^7.27.3",
- "@jridgewell/gen-mapping": "^0.3.5",
- "@jridgewell/trace-mapping": "^0.3.25",
+ "@babel/parser": "^7.28.5",
+ "@babel/types": "^7.28.5",
+ "@jridgewell/gen-mapping": "^0.3.12",
+ "@jridgewell/trace-mapping": "^0.3.28",
"jsesc": "^3.0.2"
},
"engines": {
@@ -824,6 +819,16 @@
"node": ">=6.9.0"
}
},
+ "node_modules/@babel/helper-globals": {
+ "version": "7.28.0",
+ "resolved": "https://registry.npmjs.org/@babel/helper-globals/-/helper-globals-7.28.0.tgz",
+ "integrity": "sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
"node_modules/@babel/helper-module-imports": {
"version": "7.27.1",
"dev": true,
@@ -837,13 +842,15 @@
}
},
"node_modules/@babel/helper-module-transforms": {
- "version": "7.27.3",
+ "version": "7.28.3",
+ "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.28.3.tgz",
+ "integrity": "sha512-gytXUbs8k2sXS9PnQptz5o0QnpLL51SwASIORY6XaBKF88nsOT0Zw9szLqlSGQDP/4TljBAD5y98p2U1fqkdsw==",
"dev": true,
"license": "MIT",
"dependencies": {
"@babel/helper-module-imports": "^7.27.1",
"@babel/helper-validator-identifier": "^7.27.1",
- "@babel/traverse": "^7.27.3"
+ "@babel/traverse": "^7.28.3"
},
"engines": {
"node": ">=6.9.0"
@@ -869,7 +876,9 @@
}
},
"node_modules/@babel/helper-validator-identifier": {
- "version": "7.27.1",
+ "version": "7.28.5",
+ "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.28.5.tgz",
+ "integrity": "sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==",
"dev": true,
"license": "MIT",
"engines": {
@@ -885,23 +894,27 @@
}
},
"node_modules/@babel/helpers": {
- "version": "7.27.6",
+ "version": "7.28.4",
+ "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.28.4.tgz",
+ "integrity": "sha512-HFN59MmQXGHVyYadKLVumYsA9dBFun/ldYxipEjzA4196jpLZd8UjEEBLkbEkvfYreDqJhZxYAWFPtrfhNpj4w==",
"dev": true,
"license": "MIT",
"dependencies": {
"@babel/template": "^7.27.2",
- "@babel/types": "^7.27.6"
+ "@babel/types": "^7.28.4"
},
"engines": {
"node": ">=6.9.0"
}
},
"node_modules/@babel/parser": {
- "version": "7.27.5",
+ "version": "7.28.5",
+ "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.28.5.tgz",
+ "integrity": "sha512-KKBU1VGYR7ORr3At5HAtUQ+TV3SzRCXmA/8OdDZiLDBIZxVyzXuztPjfLd3BV1PRAQGCMWWSHYhL0F8d5uHBDQ==",
"dev": true,
"license": "MIT",
"dependencies": {
- "@babel/types": "^7.27.3"
+ "@babel/types": "^7.28.5"
},
"bin": {
"parser": "bin/babel-parser.js"
@@ -1144,6 +1157,38 @@
"@babel/core": "^7.0.0-0"
}
},
+ "node_modules/@babel/plugin-transform-react-jsx-self": {
+ "version": "7.27.1",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-self/-/plugin-transform-react-jsx-self-7.27.1.tgz",
+ "integrity": "sha512-6UzkCs+ejGdZ5mFFC/OCUrv028ab2fp1znZmCZjAOBKiBK2jXD1O+BPSfX8X2qjJ75fZBMSnQn3Rq2mrBJK2mw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-plugin-utils": "^7.27.1"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0-0"
+ }
+ },
+ "node_modules/@babel/plugin-transform-react-jsx-source": {
+ "version": "7.27.1",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-source/-/plugin-transform-react-jsx-source-7.27.1.tgz",
+ "integrity": "sha512-zbwoTsBruTeKB9hSq73ha66iFeJHuaFkUbwvqElnygoNbj/jHRsSeokowZFN3CZ64IvEqcmmkVe89OPXc7ldAw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-plugin-utils": "^7.27.1"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0-0"
+ }
+ },
"node_modules/@babel/runtime": {
"version": "7.27.6",
"license": "MIT",
@@ -1165,39 +1210,33 @@
}
},
"node_modules/@babel/traverse": {
- "version": "7.27.4",
+ "version": "7.28.5",
+ "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.28.5.tgz",
+ "integrity": "sha512-TCCj4t55U90khlYkVV/0TfkJkAkUg3jZFA3Neb7unZT8CPok7iiRfaX0F+WnqWqt7OxhOn0uBKXCw4lbL8W0aQ==",
"dev": true,
"license": "MIT",
"dependencies": {
"@babel/code-frame": "^7.27.1",
- "@babel/generator": "^7.27.3",
- "@babel/parser": "^7.27.4",
+ "@babel/generator": "^7.28.5",
+ "@babel/helper-globals": "^7.28.0",
+ "@babel/parser": "^7.28.5",
"@babel/template": "^7.27.2",
- "@babel/types": "^7.27.3",
- "debug": "^4.3.1",
- "globals": "^11.1.0"
+ "@babel/types": "^7.28.5",
+ "debug": "^4.3.1"
},
"engines": {
"node": ">=6.9.0"
}
},
- "node_modules/@babel/traverse/node_modules/globals": {
- "version": "11.12.0",
- "resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz",
- "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=4"
- }
- },
"node_modules/@babel/types": {
- "version": "7.27.6",
+ "version": "7.28.5",
+ "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.28.5.tgz",
+ "integrity": "sha512-qQ5m48eI/MFLQ5PxQj4PFaprjyCTLI37ElWMmNs0K8Lk3dVeOdNpB3ks8jc7yM5CDmVC73eMVk/trk3fgmrUpA==",
"dev": true,
"license": "MIT",
"dependencies": {
"@babel/helper-string-parser": "^7.27.1",
- "@babel/helper-validator-identifier": "^7.27.1"
+ "@babel/helper-validator-identifier": "^7.28.5"
},
"engines": {
"node": ">=6.9.0"
@@ -2784,28 +2823,29 @@
}
},
"node_modules/@jridgewell/gen-mapping": {
- "version": "0.3.8",
+ "version": "0.3.13",
+ "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz",
+ "integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==",
"dev": true,
"license": "MIT",
"dependencies": {
- "@jridgewell/set-array": "^1.2.1",
- "@jridgewell/sourcemap-codec": "^1.4.10",
+ "@jridgewell/sourcemap-codec": "^1.5.0",
"@jridgewell/trace-mapping": "^0.3.24"
- },
- "engines": {
- "node": ">=6.0.0"
}
},
- "node_modules/@jridgewell/resolve-uri": {
- "version": "3.1.2",
+ "node_modules/@jridgewell/remapping": {
+ "version": "2.3.5",
+ "resolved": "https://registry.npmjs.org/@jridgewell/remapping/-/remapping-2.3.5.tgz",
+ "integrity": "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==",
"dev": true,
"license": "MIT",
- "engines": {
- "node": ">=6.0.0"
+ "dependencies": {
+ "@jridgewell/gen-mapping": "^0.3.5",
+ "@jridgewell/trace-mapping": "^0.3.24"
}
},
- "node_modules/@jridgewell/set-array": {
- "version": "1.2.1",
+ "node_modules/@jridgewell/resolve-uri": {
+ "version": "3.1.2",
"dev": true,
"license": "MIT",
"engines": {
@@ -2827,7 +2867,9 @@
"license": "MIT"
},
"node_modules/@jridgewell/trace-mapping": {
- "version": "0.3.25",
+ "version": "0.3.31",
+ "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz",
+ "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==",
"dev": true,
"license": "MIT",
"dependencies": {
@@ -3374,6 +3416,13 @@
"dev": true,
"license": "MIT"
},
+ "node_modules/@rolldown/pluginutils": {
+ "version": "1.0.0-beta.27",
+ "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.0-beta.27.tgz",
+ "integrity": "sha512-+d0F4MKMCbeVUJwG96uQ4SgAznZNSq93I3V+9NHA4OpvqG8mRCpGdKmK8l/dl02h2CCDHwW2FqilnTyDcAnqjA==",
+ "dev": true,
+ "license": "MIT"
+ },
"node_modules/@rollup/plugin-node-resolve": {
"version": "15.3.1",
"dev": true,
@@ -3418,127 +3467,435 @@
}
}
},
- "node_modules/@sideway/address": {
- "version": "4.1.5",
+ "node_modules/@rollup/rollup-android-arm-eabi": {
+ "version": "4.53.5",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.53.5.tgz",
+ "integrity": "sha512-iDGS/h7D8t7tvZ1t6+WPK04KD0MwzLZrG0se1hzBjSi5fyxlsiggoJHwh18PCFNn7tG43OWb6pdZ6Y+rMlmyNQ==",
+ "cpu": [
+ "arm"
+ ],
"dev": true,
- "license": "BSD-3-Clause",
- "dependencies": {
- "@hapi/hoek": "^9.0.0"
- }
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "android"
+ ]
},
- "node_modules/@sideway/formula": {
- "version": "3.0.1",
+ "node_modules/@rollup/rollup-android-arm64": {
+ "version": "4.53.5",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.53.5.tgz",
+ "integrity": "sha512-wrSAViWvZHBMMlWk6EJhvg8/rjxzyEhEdgfMMjREHEq11EtJ6IP6yfcCH57YAEca2Oe3FNCE9DSTgU70EIGmVw==",
+ "cpu": [
+ "arm64"
+ ],
"dev": true,
- "license": "BSD-3-Clause"
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "android"
+ ]
},
- "node_modules/@sideway/pinpoint": {
- "version": "2.0.0",
+ "node_modules/@rollup/rollup-darwin-arm64": {
+ "version": "4.53.5",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.53.5.tgz",
+ "integrity": "sha512-S87zZPBmRO6u1YXQLwpveZm4JfPpAa6oHBX7/ghSiGH3rz/KDgAu1rKdGutV+WUI6tKDMbaBJomhnT30Y2t4VQ==",
+ "cpu": [
+ "arm64"
+ ],
"dev": true,
- "license": "BSD-3-Clause"
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ]
},
- "node_modules/@sidvind/better-ajv-errors": {
- "version": "2.1.3",
+ "node_modules/@rollup/rollup-darwin-x64": {
+ "version": "4.53.5",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.53.5.tgz",
+ "integrity": "sha512-YTbnsAaHo6VrAczISxgpTva8EkfQus0VPEVJCEaboHtZRIb6h6j0BNxRBOwnDciFTZLDPW5r+ZBmhL/+YpTZgA==",
+ "cpu": [
+ "x64"
+ ],
"dev": true,
- "license": "Apache-2.0",
- "dependencies": {
- "@babel/code-frame": "^7.16.0",
- "chalk": "^4.1.0"
- },
- "engines": {
- "node": ">= 16.14"
- },
- "peerDependencies": {
- "ajv": "4.11.8 - 8"
- }
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ]
},
- "node_modules/@sidvind/better-ajv-errors/node_modules/chalk": {
- "version": "4.1.2",
+ "node_modules/@rollup/rollup-freebsd-arm64": {
+ "version": "4.53.5",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.53.5.tgz",
+ "integrity": "sha512-1T8eY2J8rKJWzaznV7zedfdhD1BqVs1iqILhmHDq/bqCUZsrMt+j8VCTHhP0vdfbHK3e1IQ7VYx3jlKqwlf+vw==",
+ "cpu": [
+ "arm64"
+ ],
"dev": true,
"license": "MIT",
- "dependencies": {
- "ansi-styles": "^4.1.0",
- "supports-color": "^7.1.0"
- },
- "engines": {
- "node": ">=10"
- },
- "funding": {
- "url": "https://github.com/chalk/chalk?sponsor=1"
- }
+ "optional": true,
+ "os": [
+ "freebsd"
+ ]
},
- "node_modules/@sinclair/typebox": {
- "version": "0.27.8",
+ "node_modules/@rollup/rollup-freebsd-x64": {
+ "version": "4.53.5",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.53.5.tgz",
+ "integrity": "sha512-sHTiuXyBJApxRn+VFMaw1U+Qsz4kcNlxQ742snICYPrY+DDL8/ZbaC4DVIB7vgZmp3jiDaKA0WpBdP0aqPJoBQ==",
+ "cpu": [
+ "x64"
+ ],
"dev": true,
- "license": "MIT"
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "freebsd"
+ ]
},
- "node_modules/@sinonjs/commons": {
- "version": "3.0.1",
+ "node_modules/@rollup/rollup-linux-arm-gnueabihf": {
+ "version": "4.53.5",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.53.5.tgz",
+ "integrity": "sha512-dV3T9MyAf0w8zPVLVBptVlzaXxka6xg1f16VAQmjg+4KMSTWDvhimI/Y6mp8oHwNrmnmVl9XxJ/w/mO4uIQONA==",
+ "cpu": [
+ "arm"
+ ],
"dev": true,
- "license": "BSD-3-Clause",
- "dependencies": {
- "type-detect": "4.0.8"
- }
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
},
- "node_modules/@sinonjs/fake-timers": {
- "version": "10.3.0",
+ "node_modules/@rollup/rollup-linux-arm-musleabihf": {
+ "version": "4.53.5",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.53.5.tgz",
+ "integrity": "sha512-wIGYC1x/hyjP+KAu9+ewDI+fi5XSNiUi9Bvg6KGAh2TsNMA3tSEs+Sh6jJ/r4BV/bx/CyWu2ue9kDnIdRyafcQ==",
+ "cpu": [
+ "arm"
+ ],
"dev": true,
- "license": "BSD-3-Clause",
- "dependencies": {
- "@sinonjs/commons": "^3.0.0"
- }
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
},
- "node_modules/@size-limit/esbuild": {
- "version": "11.2.0",
+ "node_modules/@rollup/rollup-linux-arm64-gnu": {
+ "version": "4.53.5",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.53.5.tgz",
+ "integrity": "sha512-Y+qVA0D9d0y2FRNiG9oM3Hut/DgODZbU9I8pLLPwAsU0tUKZ49cyV1tzmB/qRbSzGvY8lpgGkJuMyuhH7Ma+Vg==",
+ "cpu": [
+ "arm64"
+ ],
"dev": true,
"license": "MIT",
- "dependencies": {
- "esbuild": "^0.25.0",
- "nanoid": "^5.1.0"
- },
- "engines": {
- "node": "^18.0.0 || >=20.0.0"
- },
- "peerDependencies": {
- "size-limit": "11.2.0"
- }
+ "optional": true,
+ "os": [
+ "linux"
+ ]
},
- "node_modules/@size-limit/file": {
- "version": "11.2.0",
+ "node_modules/@rollup/rollup-linux-arm64-musl": {
+ "version": "4.53.5",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.53.5.tgz",
+ "integrity": "sha512-juaC4bEgJsyFVfqhtGLz8mbopaWD+WeSOYr5E16y+1of6KQjc0BpwZLuxkClqY1i8sco+MdyoXPNiCkQou09+g==",
+ "cpu": [
+ "arm64"
+ ],
"dev": true,
"license": "MIT",
- "engines": {
- "node": "^18.0.0 || >=20.0.0"
- },
- "peerDependencies": {
- "size-limit": "11.2.0"
- }
+ "optional": true,
+ "os": [
+ "linux"
+ ]
},
- "node_modules/@size-limit/preset-small-lib": {
- "version": "11.2.0",
+ "node_modules/@rollup/rollup-linux-loong64-gnu": {
+ "version": "4.53.5",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.53.5.tgz",
+ "integrity": "sha512-rIEC0hZ17A42iXtHX+EPJVL/CakHo+tT7W0pbzdAGuWOt2jxDFh7A/lRhsNHBcqL4T36+UiAgwO8pbmn3dE8wA==",
+ "cpu": [
+ "loong64"
+ ],
"dev": true,
"license": "MIT",
- "dependencies": {
- "@size-limit/esbuild": "11.2.0",
- "@size-limit/file": "11.2.0",
- "size-limit": "11.2.0"
- },
- "peerDependencies": {
- "size-limit": "11.2.0"
- }
+ "optional": true,
+ "os": [
+ "linux"
+ ]
},
- "node_modules/@smithy/abort-controller": {
- "version": "4.0.4",
+ "node_modules/@rollup/rollup-linux-ppc64-gnu": {
+ "version": "4.53.5",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.53.5.tgz",
+ "integrity": "sha512-T7l409NhUE552RcAOcmJHj3xyZ2h7vMWzcwQI0hvn5tqHh3oSoclf9WgTl+0QqffWFG8MEVZZP1/OBglKZx52Q==",
+ "cpu": [
+ "ppc64"
+ ],
"dev": true,
- "license": "Apache-2.0",
- "dependencies": {
- "@smithy/types": "^4.3.1",
- "tslib": "^2.6.2"
- },
- "engines": {
- "node": ">=18.0.0"
- }
- },
- "node_modules/@smithy/config-resolver": {
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-riscv64-gnu": {
+ "version": "4.53.5",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.53.5.tgz",
+ "integrity": "sha512-7OK5/GhxbnrMcxIFoYfhV/TkknarkYC1hqUw1wU2xUN3TVRLNT5FmBv4KkheSG2xZ6IEbRAhTooTV2+R5Tk0lQ==",
+ "cpu": [
+ "riscv64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-riscv64-musl": {
+ "version": "4.53.5",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.53.5.tgz",
+ "integrity": "sha512-GwuDBE/PsXaTa76lO5eLJTyr2k8QkPipAyOrs4V/KJufHCZBJ495VCGJol35grx9xryk4V+2zd3Ri+3v7NPh+w==",
+ "cpu": [
+ "riscv64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-s390x-gnu": {
+ "version": "4.53.5",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.53.5.tgz",
+ "integrity": "sha512-IAE1Ziyr1qNfnmiQLHBURAD+eh/zH1pIeJjeShleII7Vj8kyEm2PF77o+lf3WTHDpNJcu4IXJxNO0Zluro8bOw==",
+ "cpu": [
+ "s390x"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-x64-gnu": {
+ "version": "4.53.5",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.53.5.tgz",
+ "integrity": "sha512-Pg6E+oP7GvZ4XwgRJBuSXZjcqpIW3yCBhK4BcsANvb47qMvAbCjR6E+1a/U2WXz1JJxp9/4Dno3/iSJLcm5auw==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-x64-musl": {
+ "version": "4.53.5",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.53.5.tgz",
+ "integrity": "sha512-txGtluxDKTxaMDzUduGP0wdfng24y1rygUMnmlUJ88fzCCULCLn7oE5kb2+tRB+MWq1QDZT6ObT5RrR8HFRKqg==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-openharmony-arm64": {
+ "version": "4.53.5",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.53.5.tgz",
+ "integrity": "sha512-3DFiLPnTxiOQV993fMc+KO8zXHTcIjgaInrqlG8zDp1TlhYl6WgrOHuJkJQ6M8zHEcntSJsUp1XFZSY8C1DYbg==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "openharmony"
+ ]
+ },
+ "node_modules/@rollup/rollup-win32-arm64-msvc": {
+ "version": "4.53.5",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.53.5.tgz",
+ "integrity": "sha512-nggc/wPpNTgjGg75hu+Q/3i32R00Lq1B6N1DO7MCU340MRKL3WZJMjA9U4K4gzy3dkZPXm9E1Nc81FItBVGRlA==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ]
+ },
+ "node_modules/@rollup/rollup-win32-ia32-msvc": {
+ "version": "4.53.5",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.53.5.tgz",
+ "integrity": "sha512-U/54pTbdQpPLBdEzCT6NBCFAfSZMvmjr0twhnD9f4EIvlm9wy3jjQ38yQj1AGznrNO65EWQMgm/QUjuIVrYF9w==",
+ "cpu": [
+ "ia32"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ]
+ },
+ "node_modules/@rollup/rollup-win32-x64-gnu": {
+ "version": "4.53.5",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.53.5.tgz",
+ "integrity": "sha512-2NqKgZSuLH9SXBBV2dWNRCZmocgSOx8OJSdpRaEcRlIfX8YrKxUT6z0F1NpvDVhOsl190UFTRh2F2WDWWCYp3A==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ]
+ },
+ "node_modules/@rollup/rollup-win32-x64-msvc": {
+ "version": "4.53.5",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.53.5.tgz",
+ "integrity": "sha512-JRpZUhCfhZ4keB5v0fe02gQJy05GqboPOaxvjugW04RLSYYoB/9t2lx2u/tMs/Na/1NXfY8QYjgRljRpN+MjTQ==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ]
+ },
+ "node_modules/@sideway/address": {
+ "version": "4.1.5",
+ "dev": true,
+ "license": "BSD-3-Clause",
+ "dependencies": {
+ "@hapi/hoek": "^9.0.0"
+ }
+ },
+ "node_modules/@sideway/formula": {
+ "version": "3.0.1",
+ "dev": true,
+ "license": "BSD-3-Clause"
+ },
+ "node_modules/@sideway/pinpoint": {
+ "version": "2.0.0",
+ "dev": true,
+ "license": "BSD-3-Clause"
+ },
+ "node_modules/@sidvind/better-ajv-errors": {
+ "version": "2.1.3",
+ "dev": true,
+ "license": "Apache-2.0",
+ "dependencies": {
+ "@babel/code-frame": "^7.16.0",
+ "chalk": "^4.1.0"
+ },
+ "engines": {
+ "node": ">= 16.14"
+ },
+ "peerDependencies": {
+ "ajv": "4.11.8 - 8"
+ }
+ },
+ "node_modules/@sidvind/better-ajv-errors/node_modules/chalk": {
+ "version": "4.1.2",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "ansi-styles": "^4.1.0",
+ "supports-color": "^7.1.0"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/chalk?sponsor=1"
+ }
+ },
+ "node_modules/@sinclair/typebox": {
+ "version": "0.27.8",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/@sinonjs/commons": {
+ "version": "3.0.1",
+ "dev": true,
+ "license": "BSD-3-Clause",
+ "dependencies": {
+ "type-detect": "4.0.8"
+ }
+ },
+ "node_modules/@sinonjs/fake-timers": {
+ "version": "10.3.0",
+ "dev": true,
+ "license": "BSD-3-Clause",
+ "dependencies": {
+ "@sinonjs/commons": "^3.0.0"
+ }
+ },
+ "node_modules/@size-limit/esbuild": {
+ "version": "11.2.0",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "esbuild": "^0.25.0",
+ "nanoid": "^5.1.0"
+ },
+ "engines": {
+ "node": "^18.0.0 || >=20.0.0"
+ },
+ "peerDependencies": {
+ "size-limit": "11.2.0"
+ }
+ },
+ "node_modules/@size-limit/file": {
+ "version": "11.2.0",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": "^18.0.0 || >=20.0.0"
+ },
+ "peerDependencies": {
+ "size-limit": "11.2.0"
+ }
+ },
+ "node_modules/@size-limit/preset-small-lib": {
+ "version": "11.2.0",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@size-limit/esbuild": "11.2.0",
+ "@size-limit/file": "11.2.0",
+ "size-limit": "11.2.0"
+ },
+ "peerDependencies": {
+ "size-limit": "11.2.0"
+ }
+ },
+ "node_modules/@smithy/abort-controller": {
+ "version": "4.0.4",
+ "dev": true,
+ "license": "Apache-2.0",
+ "dependencies": {
+ "@smithy/types": "^4.3.1",
+ "tslib": "^2.6.2"
+ },
+ "engines": {
+ "node": ">=18.0.0"
+ }
+ },
+ "node_modules/@smithy/config-resolver": {
"version": "4.1.4",
"dev": true,
"license": "Apache-2.0",
@@ -4907,6 +5264,27 @@
"url": "https://opencollective.com/eslint"
}
},
+ "node_modules/@vitejs/plugin-react": {
+ "version": "4.7.0",
+ "resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-4.7.0.tgz",
+ "integrity": "sha512-gUu9hwfWvvEDBBmgtAowQCojwZmJ5mcLn3aufeCsitijs3+f2NsrPtlAWIR6OPiqljl96GVCUbLe0HyqIpVaoA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/core": "^7.28.0",
+ "@babel/plugin-transform-react-jsx-self": "^7.27.1",
+ "@babel/plugin-transform-react-jsx-source": "^7.27.1",
+ "@rolldown/pluginutils": "1.0.0-beta.27",
+ "@types/babel__core": "^7.20.5",
+ "react-refresh": "^0.17.0"
+ },
+ "engines": {
+ "node": "^14.18.0 || >=16.0.0"
+ },
+ "peerDependencies": {
+ "vite": "^4.2.0 || ^5.0.0 || ^6.0.0 || ^7.0.0"
+ }
+ },
"node_modules/@vitest/pretty-format": {
"version": "3.2.4",
"resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-3.2.4.tgz",
@@ -17195,6 +17573,16 @@
"version": "18.3.1",
"license": "MIT"
},
+ "node_modules/react-refresh": {
+ "version": "0.17.0",
+ "resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.17.0.tgz",
+ "integrity": "sha512-z6F7K9bV85EfseRCp2bzrpyQ0Gkw1uLoCel9XBVWPg/TjRj94SkJzUTGfOa4bs7iJvBWtQG0Wq7wnI0syw3EBQ==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
"node_modules/react-router": {
"version": "5.3.4",
"dev": true,
@@ -20717,6 +21105,538 @@
"node": ">=10.13.0"
}
},
+ "node_modules/vite": {
+ "version": "5.4.21",
+ "resolved": "https://registry.npmjs.org/vite/-/vite-5.4.21.tgz",
+ "integrity": "sha512-o5a9xKjbtuhY6Bi5S3+HvbRERmouabWbyUcpXXUA1u+GNUKoROi9byOJ8M0nHbHYHkYICiMlqxkg1KkYmm25Sw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "esbuild": "^0.21.3",
+ "postcss": "^8.4.43",
+ "rollup": "^4.20.0"
+ },
+ "bin": {
+ "vite": "bin/vite.js"
+ },
+ "engines": {
+ "node": "^18.0.0 || >=20.0.0"
+ },
+ "funding": {
+ "url": "https://github.com/vitejs/vite?sponsor=1"
+ },
+ "optionalDependencies": {
+ "fsevents": "~2.3.3"
+ },
+ "peerDependencies": {
+ "@types/node": "^18.0.0 || >=20.0.0",
+ "less": "*",
+ "lightningcss": "^1.21.0",
+ "sass": "*",
+ "sass-embedded": "*",
+ "stylus": "*",
+ "sugarss": "*",
+ "terser": "^5.4.0"
+ },
+ "peerDependenciesMeta": {
+ "@types/node": {
+ "optional": true
+ },
+ "less": {
+ "optional": true
+ },
+ "lightningcss": {
+ "optional": true
+ },
+ "sass": {
+ "optional": true
+ },
+ "sass-embedded": {
+ "optional": true
+ },
+ "stylus": {
+ "optional": true
+ },
+ "sugarss": {
+ "optional": true
+ },
+ "terser": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/vite/node_modules/@esbuild/aix-ppc64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.21.5.tgz",
+ "integrity": "sha512-1SDgH6ZSPTlggy1yI6+Dbkiz8xzpHJEVAlF/AM1tHPLsf5STom9rwtjE4hKAF20FfXXNTFqEYXyJNWh1GiZedQ==",
+ "cpu": [
+ "ppc64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "aix"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/vite/node_modules/@esbuild/android-arm": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.21.5.tgz",
+ "integrity": "sha512-vCPvzSjpPHEi1siZdlvAlsPxXl7WbOVUBBAowWug4rJHb68Ox8KualB+1ocNvT5fjv6wpkX6o/iEpbDrf68zcg==",
+ "cpu": [
+ "arm"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "android"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/vite/node_modules/@esbuild/android-arm64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.21.5.tgz",
+ "integrity": "sha512-c0uX9VAUBQ7dTDCjq+wdyGLowMdtR/GoC2U5IYk/7D1H1JYC0qseD7+11iMP2mRLN9RcCMRcjC4YMclCzGwS/A==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "android"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/vite/node_modules/@esbuild/android-x64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.21.5.tgz",
+ "integrity": "sha512-D7aPRUUNHRBwHxzxRvp856rjUHRFW1SdQATKXH2hqA0kAZb1hKmi02OpYRacl0TxIGz/ZmXWlbZgjwWYaCakTA==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "android"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/vite/node_modules/@esbuild/darwin-arm64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.21.5.tgz",
+ "integrity": "sha512-DwqXqZyuk5AiWWf3UfLiRDJ5EDd49zg6O9wclZ7kUMv2WRFr4HKjXp/5t8JZ11QbQfUS6/cRCKGwYhtNAY88kQ==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/vite/node_modules/@esbuild/darwin-x64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.21.5.tgz",
+ "integrity": "sha512-se/JjF8NlmKVG4kNIuyWMV/22ZaerB+qaSi5MdrXtd6R08kvs2qCN4C09miupktDitvh8jRFflwGFBQcxZRjbw==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/vite/node_modules/@esbuild/freebsd-arm64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.21.5.tgz",
+ "integrity": "sha512-5JcRxxRDUJLX8JXp/wcBCy3pENnCgBR9bN6JsY4OmhfUtIHe3ZW0mawA7+RDAcMLrMIZaf03NlQiX9DGyB8h4g==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "freebsd"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/vite/node_modules/@esbuild/freebsd-x64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.21.5.tgz",
+ "integrity": "sha512-J95kNBj1zkbMXtHVH29bBriQygMXqoVQOQYA+ISs0/2l3T9/kj42ow2mpqerRBxDJnmkUDCaQT/dfNXWX/ZZCQ==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "freebsd"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/vite/node_modules/@esbuild/linux-arm": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.21.5.tgz",
+ "integrity": "sha512-bPb5AHZtbeNGjCKVZ9UGqGwo8EUu4cLq68E95A53KlxAPRmUyYv2D6F0uUI65XisGOL1hBP5mTronbgo+0bFcA==",
+ "cpu": [
+ "arm"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/vite/node_modules/@esbuild/linux-arm64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.21.5.tgz",
+ "integrity": "sha512-ibKvmyYzKsBeX8d8I7MH/TMfWDXBF3db4qM6sy+7re0YXya+K1cem3on9XgdT2EQGMu4hQyZhan7TeQ8XkGp4Q==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/vite/node_modules/@esbuild/linux-ia32": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.21.5.tgz",
+ "integrity": "sha512-YvjXDqLRqPDl2dvRODYmmhz4rPeVKYvppfGYKSNGdyZkA01046pLWyRKKI3ax8fbJoK5QbxblURkwK/MWY18Tg==",
+ "cpu": [
+ "ia32"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/vite/node_modules/@esbuild/linux-loong64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.21.5.tgz",
+ "integrity": "sha512-uHf1BmMG8qEvzdrzAqg2SIG/02+4/DHB6a9Kbya0XDvwDEKCoC8ZRWI5JJvNdUjtciBGFQ5PuBlpEOXQj+JQSg==",
+ "cpu": [
+ "loong64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/vite/node_modules/@esbuild/linux-mips64el": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.21.5.tgz",
+ "integrity": "sha512-IajOmO+KJK23bj52dFSNCMsz1QP1DqM6cwLUv3W1QwyxkyIWecfafnI555fvSGqEKwjMXVLokcV5ygHW5b3Jbg==",
+ "cpu": [
+ "mips64el"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/vite/node_modules/@esbuild/linux-ppc64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.21.5.tgz",
+ "integrity": "sha512-1hHV/Z4OEfMwpLO8rp7CvlhBDnjsC3CttJXIhBi+5Aj5r+MBvy4egg7wCbe//hSsT+RvDAG7s81tAvpL2XAE4w==",
+ "cpu": [
+ "ppc64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/vite/node_modules/@esbuild/linux-riscv64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.21.5.tgz",
+ "integrity": "sha512-2HdXDMd9GMgTGrPWnJzP2ALSokE/0O5HhTUvWIbD3YdjME8JwvSCnNGBnTThKGEB91OZhzrJ4qIIxk/SBmyDDA==",
+ "cpu": [
+ "riscv64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/vite/node_modules/@esbuild/linux-s390x": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.21.5.tgz",
+ "integrity": "sha512-zus5sxzqBJD3eXxwvjN1yQkRepANgxE9lgOW2qLnmr8ikMTphkjgXu1HR01K4FJg8h1kEEDAqDcZQtbrRnB41A==",
+ "cpu": [
+ "s390x"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/vite/node_modules/@esbuild/linux-x64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.21.5.tgz",
+ "integrity": "sha512-1rYdTpyv03iycF1+BhzrzQJCdOuAOtaqHTWJZCWvijKD2N5Xu0TtVC8/+1faWqcP9iBCWOmjmhoH94dH82BxPQ==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/vite/node_modules/@esbuild/netbsd-x64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.21.5.tgz",
+ "integrity": "sha512-Woi2MXzXjMULccIwMnLciyZH4nCIMpWQAs049KEeMvOcNADVxo0UBIQPfSmxB3CWKedngg7sWZdLvLczpe0tLg==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "netbsd"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/vite/node_modules/@esbuild/openbsd-x64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.21.5.tgz",
+ "integrity": "sha512-HLNNw99xsvx12lFBUwoT8EVCsSvRNDVxNpjZ7bPn947b8gJPzeHWyNVhFsaerc0n3TsbOINvRP2byTZ5LKezow==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "openbsd"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/vite/node_modules/@esbuild/sunos-x64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.21.5.tgz",
+ "integrity": "sha512-6+gjmFpfy0BHU5Tpptkuh8+uw3mnrvgs+dSPQXQOv3ekbordwnzTVEb4qnIvQcYXq6gzkyTnoZ9dZG+D4garKg==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "sunos"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/vite/node_modules/@esbuild/win32-arm64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.21.5.tgz",
+ "integrity": "sha512-Z0gOTd75VvXqyq7nsl93zwahcTROgqvuAcYDUr+vOv8uHhNSKROyU961kgtCD1e95IqPKSQKH7tBTslnS3tA8A==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/vite/node_modules/@esbuild/win32-ia32": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.21.5.tgz",
+ "integrity": "sha512-SWXFF1CL2RVNMaVs+BBClwtfZSvDgtL//G/smwAc5oVK/UPu2Gu9tIaRgFmYFFKrmg3SyAjSrElf0TiJ1v8fYA==",
+ "cpu": [
+ "ia32"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/vite/node_modules/@esbuild/win32-x64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.21.5.tgz",
+ "integrity": "sha512-tQd/1efJuzPC6rCFwEvLtci/xNFcTZknmXs98FYDfGE4wP9ClFV98nyKrzJKVPMhdDnjzLhdUyMX4PsQAPjwIw==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/vite/node_modules/esbuild": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.21.5.tgz",
+ "integrity": "sha512-mg3OPMV4hXywwpoDxu3Qda5xCKQi+vCTZq8S9J/EpkhB2HzKXq4SNFZE3+NK93JYxc8VMSep+lOUSC/RVKaBqw==",
+ "dev": true,
+ "hasInstallScript": true,
+ "license": "MIT",
+ "bin": {
+ "esbuild": "bin/esbuild"
+ },
+ "engines": {
+ "node": ">=12"
+ },
+ "optionalDependencies": {
+ "@esbuild/aix-ppc64": "0.21.5",
+ "@esbuild/android-arm": "0.21.5",
+ "@esbuild/android-arm64": "0.21.5",
+ "@esbuild/android-x64": "0.21.5",
+ "@esbuild/darwin-arm64": "0.21.5",
+ "@esbuild/darwin-x64": "0.21.5",
+ "@esbuild/freebsd-arm64": "0.21.5",
+ "@esbuild/freebsd-x64": "0.21.5",
+ "@esbuild/linux-arm": "0.21.5",
+ "@esbuild/linux-arm64": "0.21.5",
+ "@esbuild/linux-ia32": "0.21.5",
+ "@esbuild/linux-loong64": "0.21.5",
+ "@esbuild/linux-mips64el": "0.21.5",
+ "@esbuild/linux-ppc64": "0.21.5",
+ "@esbuild/linux-riscv64": "0.21.5",
+ "@esbuild/linux-s390x": "0.21.5",
+ "@esbuild/linux-x64": "0.21.5",
+ "@esbuild/netbsd-x64": "0.21.5",
+ "@esbuild/openbsd-x64": "0.21.5",
+ "@esbuild/sunos-x64": "0.21.5",
+ "@esbuild/win32-arm64": "0.21.5",
+ "@esbuild/win32-ia32": "0.21.5",
+ "@esbuild/win32-x64": "0.21.5"
+ }
+ },
+ "node_modules/vite/node_modules/rollup": {
+ "version": "4.53.5",
+ "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.53.5.tgz",
+ "integrity": "sha512-iTNAbFSlRpcHeeWu73ywU/8KuU/LZmNCSxp6fjQkJBD3ivUb8tpDrXhIxEzA05HlYMEwmtaUnb3RP+YNv162OQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@types/estree": "1.0.8"
+ },
+ "bin": {
+ "rollup": "dist/bin/rollup"
+ },
+ "engines": {
+ "node": ">=18.0.0",
+ "npm": ">=8.0.0"
+ },
+ "optionalDependencies": {
+ "@rollup/rollup-android-arm-eabi": "4.53.5",
+ "@rollup/rollup-android-arm64": "4.53.5",
+ "@rollup/rollup-darwin-arm64": "4.53.5",
+ "@rollup/rollup-darwin-x64": "4.53.5",
+ "@rollup/rollup-freebsd-arm64": "4.53.5",
+ "@rollup/rollup-freebsd-x64": "4.53.5",
+ "@rollup/rollup-linux-arm-gnueabihf": "4.53.5",
+ "@rollup/rollup-linux-arm-musleabihf": "4.53.5",
+ "@rollup/rollup-linux-arm64-gnu": "4.53.5",
+ "@rollup/rollup-linux-arm64-musl": "4.53.5",
+ "@rollup/rollup-linux-loong64-gnu": "4.53.5",
+ "@rollup/rollup-linux-ppc64-gnu": "4.53.5",
+ "@rollup/rollup-linux-riscv64-gnu": "4.53.5",
+ "@rollup/rollup-linux-riscv64-musl": "4.53.5",
+ "@rollup/rollup-linux-s390x-gnu": "4.53.5",
+ "@rollup/rollup-linux-x64-gnu": "4.53.5",
+ "@rollup/rollup-linux-x64-musl": "4.53.5",
+ "@rollup/rollup-openharmony-arm64": "4.53.5",
+ "@rollup/rollup-win32-arm64-msvc": "4.53.5",
+ "@rollup/rollup-win32-ia32-msvc": "4.53.5",
+ "@rollup/rollup-win32-x64-gnu": "4.53.5",
+ "@rollup/rollup-win32-x64-msvc": "4.53.5",
+ "fsevents": "~2.3.2"
+ }
+ },
"node_modules/w3c-xmlserializer": {
"version": "4.0.0",
"dev": true,
diff --git a/package.json b/package.json
index 952123f6f0..9d76898c4b 100644
--- a/package.json
+++ b/package.json
@@ -21,10 +21,11 @@
"build:react18": "cross-env NODE_ENV=production REACT_VERSION=18 gulp build",
"start": "npm-run-all --parallel start:watch start:dev",
"start:watch": "gulp watch",
- "start:dev": "cross-env NODE_ENV=development webpack serve --config pages/webpack.config.cjs",
+ "start:dev": "node dev-server/dev.mjs",
+ "start:dev:server": "node dev-server/server.mjs",
"start:integ": "cross-env NODE_ENV=development webpack serve --config pages/webpack.config.integ.cjs",
"start:react18": "npm-run-all --parallel start:watch start:react18:dev",
- "start:react18:dev": "cross-env NODE_ENV=development REACT_VERSION=18 webpack serve --config pages/webpack.config.cjs",
+ "start:react18:dev": "cross-env REACT_VERSION=18 node dev-server/server.mjs",
"prepare": "husky"
},
"dependencies": {
@@ -80,6 +81,7 @@
"@types/react-test-renderer": "^16.9.12",
"@types/react-transition-group": "^4.4.4",
"@types/webpack-env": "^1.16.3",
+ "@vitejs/plugin-react": "^4.3.4",
"axe-core": "^4.7.2",
"babel-jest": "^29.7.0",
"change-case": "^4.1.2",
@@ -100,6 +102,7 @@
"eslint-plugin-simple-import-sort": "^12.1.1",
"eslint-plugin-unicorn": "^60.0.0",
"execa": "^4.1.0",
+ "express": "^4.21.2",
"fs-extra": "^11.2.0",
"glob": "^11.1.0",
"globals": "^16.1.0",
@@ -144,6 +147,7 @@
"types-react18": "npm:@types/react@^18.3.24",
"typescript": "^5.9.2",
"typescript-eslint": "^8.44.0",
+ "vite": "^5.4.21",
"wait-on": "^8.0.2",
"webpack": "^5.94.0",
"webpack-cli": "^5.1.4",
diff --git a/pages/common/flush-response.ts b/pages/common/flush-response.ts
index 697de7e450..f56684f2ee 100644
--- a/pages/common/flush-response.ts
+++ b/pages/common/flush-response.ts
@@ -8,6 +8,10 @@ export interface WindowWithFlushResponse extends Window {
declare const window: WindowWithFlushResponse;
export function enhanceWindow() {
+ // Guard for SSR - only run on client
+ if (typeof window === 'undefined') {
+ return;
+ }
window.__pendingCallbacks = [];
window.__flushServerResponse = () => {
for (const cb of window.__pendingCallbacks) {
diff --git a/pages/drag-handle/drag-handle-interaction-state-hook.page.tsx b/pages/drag-handle/drag-handle-interaction-state-hook.page.tsx
index b1bee04708..47323722ec 100644
--- a/pages/drag-handle/drag-handle-interaction-state-hook.page.tsx
+++ b/pages/drag-handle/drag-handle-interaction-state-hook.page.tsx
@@ -136,7 +136,11 @@ const TestBoardItemButton: React.FC = () => {
- {renderInPortal ? {createPortal(buttonComp, document.body)}
: buttonComp}
+ {renderInPortal ? (
+ {typeof document !== 'undefined' && createPortal(buttonComp, document.body)}
+ ) : (
+ buttonComp
+ )}
>
);
diff --git a/pages/funnel-analytics/with-async-table.page.tsx b/pages/funnel-analytics/with-async-table.page.tsx
index c36c17b618..b3f053d79b 100644
--- a/pages/funnel-analytics/with-async-table.page.tsx
+++ b/pages/funnel-analytics/with-async-table.page.tsx
@@ -31,7 +31,10 @@ import {
} from '../table/shared-configs';
const componentMetricsLog: any[] = [];
-(window as any).__awsuiComponentlMetrics__ = componentMetricsLog;
+// Guard for SSR - only set on client
+if (typeof window !== 'undefined') {
+ (window as any).__awsuiComponentlMetrics__ = componentMetricsLog;
+}
setComponentMetrics({
componentMounted: props => {
diff --git a/pages/onboarding/with-app-layout.page.tsx b/pages/onboarding/with-app-layout.page.tsx
index 6fdb2cade0..84826caad4 100644
--- a/pages/onboarding/with-app-layout.page.tsx
+++ b/pages/onboarding/with-app-layout.page.tsx
@@ -96,7 +96,10 @@ export default function OnboardingDemoPage() {
}, []);
const onFeedbackClick = useCallback(() => {
- window.prompt('Please enter your feedback here:');
+ // Guard for SSR
+ if (typeof window !== 'undefined') {
+ window.prompt('Please enter your feedback here:');
+ }
}, []);
return (
@@ -127,7 +130,7 @@ export default function OnboardingDemoPage() {
i18nStrings={tutorialPanelStrings}
tutorials={tutorials}
onFeedbackClick={onFeedbackClick}
- downloadUrl={window.location.href}
+ downloadUrl={typeof window !== 'undefined' ? window.location.href : ''}
/>
),
},
diff --git a/pages/select/select.test.async.page.tsx b/pages/select/select.test.async.page.tsx
index 84b03413ec..74d32bcad0 100644
--- a/pages/select/select.test.async.page.tsx
+++ b/pages/select/select.test.async.page.tsx
@@ -22,7 +22,8 @@ interface ExtendedWindow extends Window {
__pendingRequests: Array;
}
declare const window: ExtendedWindow;
-const pendingRequests: Array = (window.__pendingRequests = []);
+// Guard for SSR - only initialize on client
+const pendingRequests: Array = typeof window !== 'undefined' ? (window.__pendingRequests = []) : [];
const ITEMS_PER_PAGE = 25;
const MAX_PAGES = 3;
diff --git a/pages/select/select.test.events.page.tsx b/pages/select/select.test.events.page.tsx
index b18de8a519..c20bfc278c 100644
--- a/pages/select/select.test.events.page.tsx
+++ b/pages/select/select.test.events.page.tsx
@@ -17,13 +17,26 @@ interface ExtendedWindow extends Window {
}
declare const window: ExtendedWindow;
const appendLog = (text: string) => {
+ // Guard for SSR
+ if (typeof window === 'undefined') {
+ return;
+ }
if (!window.__eventsLog) {
window.__eventsLog = [];
}
window.__eventsLog.push(text);
};
-const clearLog = () => (window.__eventsLog = []);
-window.__clearEvents = clearLog;
+const clearLog = () => {
+ // Guard for SSR
+ if (typeof window === 'undefined') {
+ return;
+ }
+ window.__eventsLog = [];
+};
+// Guard for SSR - only set on client
+if (typeof window !== 'undefined') {
+ window.__clearEvents = clearLog;
+}
export default function SelectEventsPage() {
const [selectedOption, setValue] = useState(null);
diff --git a/pages/select/virtual-resize.page.tsx b/pages/select/virtual-resize.page.tsx
index d9d30ed1bc..6d07314354 100644
--- a/pages/select/virtual-resize.page.tsx
+++ b/pages/select/virtual-resize.page.tsx
@@ -24,7 +24,10 @@ const options = [
export default function () {
const [selectedOption, setSelectedOption] = useState(null);
const [shrunk, setShrunk] = useState(false);
- window.__shrinkComponent = setShrunk;
+ // Guard for SSR - only set on client
+ if (typeof window !== 'undefined') {
+ window.__shrinkComponent = setShrunk;
+ }
const style = shrunk ? { width: '100px' } : undefined;
return (
diff --git a/pages/theming/themed-stroke-width.page.tsx b/pages/theming/themed-stroke-width.page.tsx
index e9b0ee7a3f..762409ee98 100644
--- a/pages/theming/themed-stroke-width.page.tsx
+++ b/pages/theming/themed-stroke-width.page.tsx
@@ -55,6 +55,10 @@ export default function ThemedStrokeWidthPage() {
// Reload page once after initial load to fix theme application
useLayoutEffect(() => {
+ // Guard for SSR
+ if (typeof window === 'undefined' || typeof sessionStorage === 'undefined') {
+ return;
+ }
const hasReloaded = sessionStorage.getItem('themed-stroke-width-reloaded');
if (!hasReloaded) {
sessionStorage.setItem('themed-stroke-width-reloaded', 'true');
diff --git a/pages/tree-view/permutations.page.tsx b/pages/tree-view/permutations.page.tsx
index ca0d4f564c..5d29a2dd8c 100644
--- a/pages/tree-view/permutations.page.tsx
+++ b/pages/tree-view/permutations.page.tsx
@@ -54,7 +54,10 @@ export default function TreeViewPermuations() {
checked={urlParams.expandAll ?? false}
onChange={event => {
setUrlParams({ expandAll: event.detail.checked });
- window.location.reload();
+ // Guard for SSR
+ if (typeof window !== 'undefined') {
+ window.location.reload();
+ }
}}
>
Expand all