Web Frontend Development Guidelines¶
Version History¶
| Version | Date | Description/Updates |
|---|---|---|
| 1.0 | April 2026 | Publish to greater DHI community |
| 0.3 | Feb 2026 | Updates after second stakeholder review |
| 0.2 | Nov 2025 | Updates after first stakeholder review |
| 0.1 | Aug 2025 | Initial Content |
Introduction¶
Defines the goals and scope of these guidelines so teams understand when and how to apply them. Provides a common standard for DHI web front-end development to improve consistency, maintainability, and collaboration while still allowing room for innovation.
Contacts¶
- Guidelines owner/driver: Dylan Kime
- Task force:
- Stakeholders:
Roles to Apply¶
Development roles:
- Architects
- Frontend developers
- Product Manager/Product Owners
Enforcement Levels¶
For terminology see Guideline Enforcement Levels
| Section | Enforcement Level |
|---|---|
| Core Stack Overview | Expected |
| Principles and Exceptions | Important |
| Development Environment | Important |
| Code and Architecture | Important |
| State Management | Expected |
| Architecture Approaches | Expected |
| Recommended Packages | DHI good practice |
| Testing | Important |
| Accessibility (a11y) | Important |
| Performance | Important |
| Security | Mandatory |
| Code Reviews | Important |
References¶
Core Stack Overview¶
Defines the baseline technologies used across DHI web projects for consistency and reuse. Standardizing on a core stack enables code sharing, simplifies hiring, and lets developers move between projects without re-learning fundamentals.
Application Types¶
- Prefer SPA (Single Page Application) architecture with client-side routing
- Avoid SSR frameworks (for example Next.js) unless an approved exception exists
- For mobile applications use React Native
Technologies¶
| Area | Default | Notes |
|---|---|---|
| UI framework | React | Declarative components, strong ecosystem, widely adopted at DHI |
| Language | TypeScript | Static typing, safer refactoring, strong IDE support |
| State management | MobX or Zustand | MobX: Reactive state, minimal boilerplate, supports complex interconnected state. Zustand: Lightweight, unopinionated state store with a simple hook‑based API. |
| Styling | CSS-in-JS (Emotion / Styled Components) | Co-located styles, scoped automatically, dynamic styling |
Principles and Exceptions¶
Defines how to balance consistency with pragmatic exceptions across projects. Shared principles make it clearer when to stick to defaults and when a justified exception is appropriate, avoiding both rigid conformity and chaotic inconsistency.
General Principles¶
- Baseline: All applications use React + MUI + TypeScript by default
- Consistency first: Reduces onboarding time and enables cross-team collaboration
- Pragmatism over rigidity: Innovation is encouraged when requirements demand it
Acceptable Exceptions¶
- Offline-first / low-bandwidth: Service workers, IndexedDB, server components
- High-performance: WebGL/Canvas-based visualizations, heavy dataflow
- Lightweight: Landing pages where React/MUI add unnecessary overhead
Exception Process¶
- Document the reason for deviation
- Propose alternative with trade-offs
- If the deviation is for a single component, prototype project, or single client project with a handful of users then discuss with at least one other senior developer as a sanity check. If this is a high impact project with a high budget, high strategic value, or large user base then contact the architecture board for review.
- Ensure alignment with accessibility, security, and maintainability standards
Documentation & Knowledge Sharing¶
Defines a standard README structure so developers can quickly orient themselves in any DHI project. Good documentation also reduces onboarding time, prevents knowledge silos, and keeps projects maintainable as team members change.
Every project README.md should include:
| Section | Contents |
|---|---|
| Identity | Project name, CI status badge, purpose summary |
| Metadata | Maconomy ID, department, client, key roles (PO, PM, Dev Lead) |
| Tech Stack | Node version, critical libraries, auth method (IAMv2, OAuth). Note any deviations from baseline |
| Environments | External APIs consumed, DEV/PROD deployment links, local .env configuration |
| Design | Link to Figma/Miro source of truth |
Development Environment¶
Defines the recommended local tooling and setup to reduce "works on my machine" issues. Consistent environments reduce setup friction for new developers and minimise environment-related bugs.
Environment Isolation¶
| Tool | Use Case |
|---|---|
| PNPM | Node.js version management via pnpm env (see Dependency Management) |
| NVM | Manage multiple Node.js versions per project |
| VS Code Dev Containers | Project-specific environment with pre-installed dependencies |
Code Editor¶
| Item | Recommendation |
|---|---|
| Editor | VS Code (recommended) |
| Extensions | ESLint, GitLens, Error Lens |
Monorepo vs Standalone¶
| Option | When to use | Notes |
|---|---|---|
| Standalone (default) | Most projects; different teams/budgets or limited shared code | Separate repository per project |
| Monorepo | Shared libraries or tightly related applications | Use PNPM workspaces with Turborepo |
Note:
NXis losing community support so it has been removed as a recommendation
Dependency Management¶
Defines the recommended package manager and supporting practices.
Package managers¶
| Manager | When to use | Why |
|---|---|---|
| PNPM (recommended) | All new projects and shared workspaces | Disk-efficient centralized store, strict dependency resolution, built-in Node.js version management via pnpm env |
| NPM | Legacy projects or where PNPM is not yet adopted | Standard, bundled with Node.js |
Corepack¶
- Use Corepack to ensure the correct package manager version per project based on the
packageManagerfield inpackage.json.
Practices¶
- Commit lock files (
pnpm-lock.yaml,package-lock.json) to ensure reproducible builds - Lock versions explicitly — avoid floating ranges (
^,latest,*,1.x.x) for production dependencies until an intentional upgrade - Separate devDependencies from production dependencies to reduce bundle size
- Run regular audits —
pnpm auditornpm auditto check for vulnerabilities
Build Tools¶
| Tool | Purpose |
|---|---|
| Vite (recommended) | Fast dev server with HMR and optimized production builds |
Useful Plugins¶
| Plugin | When to use | Why |
|---|---|---|
| observing-components | Using MobX stores with React components that should react to observable state | Removes manual observer() wrappers by wrapping components at build time via Babel/SWC, reducing missed components and boilerplate |
Browser Targets¶
| Scope | Recommendation |
|---|---|
| Default | Support the latest two versions of Chrome |
| Per project | Define exact browser support (Chrome, Edge, Firefox, Safari) in the project README or documentation |
Code and Architecture¶
Defines conventions for structure, layering, and style so codebases stay predictable, navigable, and easy to review.
File and Folder Structure¶
- Workspace imports: Use absolute imports via
~/or@/, use relative import within local folder (whichever path is smaller) - Configure this in
tsconfig.jsonandvite.config.ts - Naming:
- Prefer to name files to mirror the name of the main exported symbol
SomeComponent.tsxexportsSomeComponentuseRootStore.tsexportsuseRootStore
- For collections of functionality:
index.tsas a barrel filetheming.tsfor theme related exportsmapUtils.tsfor map-related utility functions
- Exports:
- Avoid
defaultexports- Named exports are explicit and make refactoring easy and it also avoids duplication of symbols
Principles¶
| Principle | Guideline |
|---|---|
| SRP | One responsibility per module/component |
| DRY | Avoid repetition; don't over-abstract prematurely |
| ISP | Keep props and APIs small and focused |
| Size | Aim for ~300 lines per file; extract hooks/subcomponents as needed |
Separate business logic (stores, hooks) from presentation (components).
Coding Style¶
General¶
- Functional programming style only (no class-based React components, .map() over for loops & impertive code)
- Explicitly type all parameters and important return types
Styling¶
- Prefer CSS-in-JS using Emotion for co-located, theme-aware styles
- Use Emotion's
cssprop pattern for small, component-local styles where it keeps JSX readable - Tailwind CSS is acceptable for utility-first styling where it clearly benefits the project and the team agrees on its use
- Avoid inline
stylefor anything non-trivial; centralize styles in CSS-in-JS or Tailwind utilities
Commenting¶
- English only; comments explain why, not what
- Document: domain constraints, performance trade-offs, third-party workarounds
- TODO/FIXME allowed with ticket links; no commented-out code in main branches
State Management¶
Defines how to handle global, local, and cross-cutting state consistently across applications.
| Type | Solution |
|---|---|
| Global / client state | MobX (recommended) or Zustand / Jotai |
| Local UI state | useState / useReducer for trivial, short-lived state only |
| Cross-cutting concerns | Context API (theme, auth, feature flags) — keep small and focused |
Architecture Approaches¶
Defines the primary architectural models for structuring state and data flow; choose one main model per project. Avoid mixing patterns that duplicate responsibility or create multiple sources of truth.
✅ Preferred: State-centric (layered / store-driven)¶
Recommended for medium–large and long-lived applications.
- Stores/services own data fetching, caching, transformations, and business logic
- For server-side data, prefer
fetchwrapped in services or integrate TanStack Query via mobx-tanstack-query inside stores (not in components) - Derived/computed state lives in the store layer
- Components focus on rendering and user interaction only
- Single source of truth for application state
- Easier debugging and reasoning about data flow
- Fewer sync issues and less glue code
- Proven to scale better for complex apps and larger teams
Derived State¶
The principle behind derived state is single source of truth: any piece of data should have exactly one authoritative location. When you store data that can be calculated from other state, you create multiple sources of truth that inevitably drift out of sync. Derived state eliminates this category of bugs entirely by computing values on demand rather than storing them.
Why it matters:
- Eliminates sync bugs — Redundant state becomes stale when source data changes; derived state is always current
- Reduces complexity — Fewer state updates to coordinate, less code to maintain
- Improves reliability — No possibility of forgetting to update a cached copy
Example: Each layer derives from the previous; only raw API data is stored:
- Stored:
stationData?: StationDto[]— raw JSON from API response - Derived:
get stations()— transforms raw data into domain models:this.stationData.map(s => new Station(s)) - Derived:
get activeStations()— filters domain models:this.stations.filter(s => s.id === this.activeStationId)
This means that fetching new stationData or updating activeStationId will cascade through the layers automatically.
MobX get accessors handle memoization and reactivity automatically.
Alternative: Component-centric (colocated/server hooks)¶
Suitable for small–medium apps or when not using a global store.
- Components own data fetching and caching (e.g., TanStack Query / React Query hooks)
- State is colocated with the UI that consumes it
- Minimal or no global state layer
- Faster to scaffold simple features
- Less upfront structure
- Logic can become scattered and duplicated as apps grow
- Harder to share derived data or coordinate behavior across features
UX Design¶
Defines expectations for collaborating with designers and delivering responsive, user-friendly interfaces. Clear collaboration and shared responsive design expectations help prevent miscommunication and costly rework.
Design Workflow¶
- Receive annotated designs from Figma/Miro; kickoff meeting recommended
- DHI designers may have UX or engineering backgrounds — adjust communication accordingly
Responsive Design¶
- Default target: desktop; define mobile targets per project
- Use MUI breakpoints or CSS media queries
- Test with browser DevTools; consider touch interactions for mobile
Recommended Packages¶
Defines default libraries for common frontend needs so teams benefit from shared experience and compatible choices. Standardizing on proven packages reduces decision fatigue and helps teams build collective expertise.
UI Component Library¶
| Library | Best For |
|---|---|
| MUI | Comprehensive components, consistent design language, accessibility built-in, strong theming |
| Ant Design | Enterprise applications, rich data display components (tables, forms), more ergonomic component API |
MUI is the default choice. Ant Design is a solid more performant/simpler alternative, especially for data-heavy admin interfaces.
Charting¶
| Feature | ECharts | Plotly | Highcharts |
|---|---|---|---|
| Pros | High performance, rich chart types, flexible config | Interactive, scientific charts, good docs | Mature, excellent docs, accessibility |
| Cons | Smaller community, less intuitive API | Larger bundle, limited styling flexibility | Commercial license required |
| React | ✅ echarts-for-react | ✅ react-plotly.js | ✅ highcharts-react-official |
| TypeScript | ⚠️ Partial (DefinitelyTyped) | ✅ Native types | ⚠️ Partial (improving) |
| Performance | ✅ Excellent | ⚠️ Moderate | ✅ Good |
| Licensing | ✅ Free (Apache 2.0) | ✅ Free (MIT) | ⚠️ Commercial |
Summary: ECharts for high-performance/config-heavy apps; Plotly for scientific/interactive visualizations; Highcharts for enterprise with strong docs (licensing cost).
Mapping¶
| Feature | Mapbox GL JS/MapLibre | OpenLayers | Leaflet |
|---|---|---|---|
| Pros | Beautiful vector maps, WebGL performance, rich styling | Powerful GIS operations, flexible | Lightweight, large plugin ecosystem |
| Cons | Token required (usage limits), commercial for advanced use | Steep learning curve, verbose API | Limited vector/WebGL support |
| React | ✅ react-map-gl | ⚠️ Community wrappers | ✅ react-leaflet |
| TypeScript | ✅ Native types | ✅ Excellent | ⚠️ Partial |
| Performance | ✅ Excellent (WebGL) | ✅ Good for GIS | ⚠️ Moderate |
| Licensing | ⚠️ Free tier with limits; Mapbox commercial / MapLibre free (BSD) | ✅ Free (BSD) | ✅ Free (BSD) |
deck.gl integrates as high-performance WebGL layers within Mapbox/MapLibre for large-scale data visualization.
Forms¶
| Approach | Best For |
|---|---|
| MobX + MUI/AntD | Complex interdependent fields, reactive validation |
| React Hook Form + Zod | Performance-critical, schema-based validation |
Provide clear validation messages; field-level validation for immediate feedback.
Network Requests¶
| Library | Purpose |
|---|---|
fetch (built-in) |
Standard browser HTTP client; wrap in small helpers/services for base URL, headers, and error handling |
| TanStack Query (React Query) | Server state, caching, background refetch via hooks |
| tRPC | Type-safe end-to-end RPC between TypeScript backend and frontend; eliminates manual REST types and reduces client/server drift |
| mobx-tanstack-query | Integrates TanStack Query caching/invalidations into MobX stores in state-centric apps |
API specs: Generate TypeScript types from OpenAPI (orval, openapi-typescript) or GraphQL (graphql-codegen). APIs under DHI control should provide specs.
Internationalization (I18n)¶
react‑i18next — i18next-based internationalization with hooks, namespaced translation files, interpolation, and runtime language switching.
Use descriptive message IDs (dashboard.header.title); format dates/numbers with locale-aware formatters.
Testing¶
Defines the recommended testing tools and strategy so teams can refactor safely and catch regressions early. Automated tests also act as living documentation of expected behavior and help control long-term maintenance costs.
| Tool | Purpose |
|---|---|
| Vitest | Unit and integration tests |
| Playwright | E2E testing |
| Storybook | Component documentation and visual testing |
| MSW | API mocking |
- Co-locate unit tests:
utils.ts→utils.test.ts - Focus on user behavior, not implementation details
- Unit testing components is largley unecessary on the frontend
- Prefer to focus on stories, Storybook snapshots, integration tests and E2E tests
Accessibility (a11y)¶
Defines baseline accessibility expectations so applications work for all users and meet legal requirements. Good accessibility also improves overall usability through clearer structure, better contrast, and keyboard-friendly interactions.
- Semantic HTML (
<button>,<nav>,<main>) - Keyboard navigation for all interactive elements
- WCAG 2.1 AA color contrast (4.5:1 normal, 3:1 large text)
- Use eslint-plugin-jsx-a11y for static analysis
- MUI has built-in a11y; add labels for form controls and icon-only buttons
Performance¶
Defines performance targets and strategies to keep applications fast and responsive in production. Proactive performance work is far cheaper than fixing user-facing issues after launch and directly affects business outcomes.
Targets: LCP < 2.5s, FID < 100ms, CLS < 0.1
| Area | Strategies |
|---|---|
| Bundle | Code splitting (React.lazy), tree-shaking, bundle analysis |
| Rendering | Virtualization (react-window) |
| Data | Efficient server-state management (e.g., TanStack Query in component-centric apps, or well-structured MobX stores in state-centric apps); avoid unnecessary refetches |
| Assets | WebP images, CDN, gzip/brotli compression |
Error Handling¶
Defines patterns for capturing, reporting, and surfacing errors so failures are visible and user impact is minimized. Robust error handling prevents cascading failures and turns potential crises into manageable incidents.
- Error boundaries: Wrap feature areas to catch rendering errors
- API errors: Handle at service layer; show user-friendly messages
- Monitoring: Use Sentry for production error tracking with context (user, route, state)
- Logging: No sensitive data; guard console statements in production
Security¶
Defines baseline security practices to protect data, reduce risk, and bake security into everyday development. Addressing security early avoids leaks, reputational damage, and expensive retrofits later.
| Concern | Prevention |
|---|---|
| XSS | Avoid dangerouslySetInnerHTML; use DOMPurify |
| CSRF | CSRF tokens, SameSite cookies |
| Secrets | Never in client code; use env variables |
| Dependencies | Regular npm audit / pnpm audit |
Auth: Use IAMv2/OAuth 2.0/OIDC. Prefer httpOnly cookies over localStorage. Server-side authorization is mandatory; client-side is UX only.
Code Reviews¶
Defines expectations for review scope, checklist use, and feedback so changes stay aligned with team standards. Reviews catch bugs, spread knowledge, and are an investment in long-term maintainability and team growth.
Checklist¶
- [ ] Follows conventions
- [ ] Components sized appropriately
- [ ] Business/presentation logic separated
- [ ] Error cases handled
- [ ] Tests included for new or alterned functionality
- [ ] No security issues
- [ ] Performance considered
Process: PRs should be small, focused on single feature per PR, clear descriptions, constructive feedback.
Useful Links¶
Provides curated references for patterns, tooling, and DHI resources worth revisiting over time. These links offer deeper dives into patterns, best practices, and DHI-specific tooling.
React Patterns & Architecture:
- https://www.patterns.dev/react/ — Modern React patterns
- https://refactoring.guru/design-patterns — General design patterns
Learning Resources:
- https://www.youtube.com/@cosdensolutions — React tutorials
DHI Resources:
- https://github.com/DHI — DHI public repositories
- https://github.com/DHI-GRAS/ — DHI GRAS repositories
- https://www.npmjs.com/package/@dhi-gras/builder — DHI GRAS builder package