---
source_hash: "ee441f51"
title: "Architecture"
weight: 60
---

# Architecture

This page describes the file organization of a SmartMaker application. Unlike a regular React project, **file location has technical consequences**: it determines the ability to extract a feature to another application, and a poor database layer split causes a loading error that is difficult to diagnose.

## Overview

```
mobile/
├── public/
│   ├── images/              # PWA icons and static images
│   └── locales/
│       └── <lang>/
│           └── <feature>.json   # one file per language AND per feature
├── src/
│   ├── api/
│   │   ├── index.js
│   │   └── mapping/
│   │       └── <feature>.js     # backend <-> front mapping, one per feature
│   ├── db/
│   │   ├── index.js             # Db instantiation
│   │   └── stores/
│   │       └── <feature>/
│   │           ├── indexes.js   # Dexie schema, NO imports
│   │           ├── useDb<Feature>.jsx   # business CRUD hook
│   │           └── index.js     # barrel
│   ├── components/
│   │   ├── app/                 # Router, Provider, Head, Toaster
│   │   ├── layouts/             # shared layouts
│   │   ├── global/              # cross-cutting application components
│   │   └── pages/
│   │       ├── public/<Page>/   # unauthenticated pages
│   │       ├── private/<Page>/  # authenticated pages
│   │       └── errors/<Page>/
│   ├── global-state/
│   │   └── slices/              # UI state only
│   ├── hooks/                   # cross-cutting application hooks
│   ├── i18n/
│   │   └── index.js
│   ├── utils/
│   │   ├── constants/
│   │   ├── functions/
│   │   └── maps/
│   │       ├── form.jsx         # field type -> input component
│   │       └── list.jsx         # column type -> cell component
│   ├── assets/                  # files handled by compilation
│   ├── appConfig.js
│   ├── main.jsx                 # entry point
│   └── sw.js                    # Service Worker (injectManifest mode)
├── .env                         # environment variables, not versioned
├── .env.example
├── index.html
├── eslint.config.js
├── package.json
└── vite.config.js
```

An application generated by SmartBoot comes with this tree structure already in place, with an example for each location: a complete `users` store (`indexes.js` and `useDbUsers`), a commented `api/mapping/`, the maps `form.jsx` and `list.jsx`, and five translation namespaces. You fill it, you don't have to create it.

## The Guiding Principle: One Feature, One Folder

A business feature (interventions, tasks, quotes, inventory) must be able to be **copied as-is** to another SmartMaker application. In concrete terms, it is distributed across three locations and three only:

| Location | Content |
| --- | --- |
| `db/stores/<feature>/` | Dexie schema and CRUD hook |
| `api/mapping/<feature>.js` | conversion between backend and front format |
| `locales/<lang>/<feature>.json` | translations for the feature namespace |

The test is simple: copy these three elements to another project, add the route, and it should work without any other modification. If an import "leaks" to a file specific to the original application, it's a flaw to be corrected.

## The Data Layer

### All CRUD in One Hook

Each feature exposes a `useDb<Feature>` hook that carries all local database accesses.

```javascript
// src/db/stores/interventions/useDbInterventions.jsx
import { db } from "src/db";

export const useDbInterventions = () => {
  const list = async (filters) => { /* ... */ };
  const get = async (id) => { /* ... */ };
  const create = async (payload) => { /* ... */ };
  const update = async (id, payload) => { /* ... */ };
  const remove = async (id) => { /* ... */ };

  return { list, get, create, update, remove };
};
```

The hook only knows about the `Db` class, `useGlobalStates` and `useApi`. Nothing else from the host project.

> [!WARNING]
> Never put Dexie calls directly in a page component. This is the most common anti-pattern, and it makes a feature impossible to extract later.

The hook name follows the form `useDb<Feature>`: `useDbInterventions`, `useDbTasks`, `useDbProducts`. Forms like `use<Feature>Services` found in older projects are legacy to be renamed.

### The indexes.js File, and Why It's Mandatory

> [!IMPORTANT]
> The Dexie schema for each feature must live in an `indexes.js` file **with no imports**, and `db/index.js` must import it directly, without going through the barrel.

Without this separation, the import graph forms a cycle:

```
db/index.js
  -> import { tasksIndexes } from "./stores"        (barrel)
  -> stores/tasks/index.js
       export const tasksIndexes = "..."
       export * from "./useDbTasks"                 <- pulls the hook
  -> useDbTasks.jsx
       import { db } from "src/db"                  <- back to start
```

At compile time, this cycle is partially unwound by Vite and produces a `Cannot access 'tasksIndexes' before initialization` error when loading the bundle. The symptom is cruel: `npm run build` succeeds, because it doesn't load the bundle, and the application crashes on startup with an error that seems to come from elsewhere (broken authentication, login loop).

The solution, which breaks the cycle by making `indexes.js` a leaf in the graph:

```javascript
// src/db/stores/tasks/indexes.js -- ZERO import
export const tasksIndexes = "++id, ref, status, updatedAt";
```

```javascript
// src/db/index.js -- imports LEAFS, not the barrel
import { tasksIndexes } from "./stores/tasks/indexes";
import { projectsIndexes } from "./stores/projects/indexes";

export const db = new Db({
  name: "myapp",
  version: 1,
  stores: { tasks: tasksIndexes, projects: projectsIndexes },
}).db;

export * from "./stores";   // acceptable here: db is already built
```

The barrel `stores/<feature>/index.js` remains normally usable by pages and other hooks. Only `db/index.js` must shortcut.

### Where Data Goes

| Nature | Location |
| --- | --- |
| persisted business data (interventions, quotes, photos) | `Db` (Dexie), via `useDb<Feature>` |
| persistent UI state (theme, language, filters, last page) | `useGlobalStates` (Redux and redux-persist) |
| ephemeral UI state (form in progress, open modal) | `useState` or `useStates` local |

> [!WARNING]
> Never place a list of business objects in a Redux slice. It must live in Dexie, and components read it via the feature's hook.

## Organizing Components

This is the most frequently asked question. The classification rule is one sentence: **we classify a component according to its scope, not according to its subject**.

| Folder | What to put there | Membership test |
| --- | --- | --- |
| `components/app/` | application plumbing: `Router`, provider composition, `Head`, `Toaster` | there is only one instance in the application |
| `components/layouts/` | layouts shared by multiple pages | it wraps pages, it is not one |
| `components/global/` | cross-cutting components reused by multiple pages | it is imported by at least two pages |
| `components/pages/<visibility>/<Page>/` | a page, mounted on a route | it corresponds to a `Router` entry |

Pages are then organized by visibility: `public/` for what is accessible without authentication, `private/` for the rest, `errors/` for error pages. This split is not decorative: it reflects what the `RouteGuard` protects and immediately makes visible which page is exposed.

### One Component, One Folder

Each component has its own folder with an `index.jsx`, even if it's alone. This allows later adding, in the same place, its styles, sub-components and tests, without moving or correcting a single import.

```
components/pages/private/InterventionPage/
├── index.jsx          # the page
├── Header/
│   └── index.jsx      # sub-component specific to this page
└── LinesTable/
    └── index.jsx
```

> [!TIP]
> A sub-component used by only one page remains **in that page's folder**. It only moves to `components/global/` when a second page uses it. Moving in anticipation clutters the common space with components that are not shared by anyone.

### A Page Should Know Nothing About the Application

A business page must be able to be mounted in the router of another application without modification.

To do:
- receive its dependencies through SmartCommon hooks (`useNavigation`, `useApi`, `useGlobalStates`) or through its props
- pass form components through the `utils/maps/form.jsx` map, so that the host application can override them by field type
- expose navigation paths in a function, rather than hardcoding `navigate('/interventions/123')`

Not to do:
- import `appConfig` from a business page
- read a Redux slice specific to the host application

## Translations: One Namespace per Feature

```
public/locales/
├── fr/
│   ├── common.json
│   ├── interventions.json
│   └── products.json
└── en/
    ├── common.json
    ├── interventions.json
    └── products.json
```

```javascript
const { t } = useTranslation('interventions');
```

All namespaces must be declared in the i18next configuration to be preloaded: this is essential for offline operation, as the Service Worker embeds them via its `**/*.json` pattern.

> [!WARNING]
> Without `Suspense`, add `react: { useSuspense: false }` to the i18next configuration. Otherwise, a page reached by direct navigation before the end of loading its namespace remains frozen on raw keys.

The `keyPrefix` should never be hardcoded in a reusable hook: it is received as a parameter, with a default value.

```javascript
// To avoid
const useIntStatuses = () => useTranslation(undefined, { keyPrefix: 'intStatuses' });

// Preferred
const useIntStatuses = (keyPrefix = 'intStatuses') => useTranslation(undefined, { keyPrefix });
```

## Configuration: No Hardcoded Values

Everything that changes from one project or environment to another goes through a Vite environment variable or a provider prop.

| Variable | Role |
| --- | --- |
| `VITE_API_URL` | Backend URL; forced relative in production build |
| `VITE_APP_NAME` | application name |
| `VITE_APP_VERSION` | version and build number, injected by Makefile |
| `VITE_LOCALES` | list of languages, derived from subdirectories of `public/locales/` |

`appConfig.js` can centralize these values, but should only be imported from the application shell, never from a business feature.

## Files at the Root of mobile/

| File | Role |
| --- | --- |
| `.env` | environment variables, ignored by Git |
| `.env.example` | template for `.env`, versioned |
| `index.html` | page where React mounts the application; carries the link to the manifest |
| `vite.config.js` | Vite configuration, including PWA |
| `eslint.config.js` | lint configuration |
| `package.json` | dependencies and scripts |

## Review Checklist

To be completed before opening a merge request, or when taking over an existing project:
- [ ] all business CRUD is in `useDb<Feature>`, no Dexie calls in a page
- [ ] each `db/stores/<feature>/` has its `indexes.js` without imports, and `db/index.js` imports the leaves
- [ ] the backend/front mapping is isolated in `api/mapping/<feature>.js`
- [ ] no business data in a Redux slice
- [ ] no business page imports `appConfig` or a slice specific to the application
- [ ] one i18n namespace per feature, all declared in the configuration
- [ ] no `fetch` or `axios`: everything goes through `useApi`
- [ ] no hardcoded URLs or ports
- [ ] one component, one folder; sub-components stay with their page as long as they only serve it

## See Also
- [Components and Pages](/front/composants-et-pages)
- [Data Storage](/front/stockage-de-donnees)
- [Offline Synchronization](/front/synchronisation)
- [Translations](/front/traductions)
- [PWA](/front/pwa) - Service Worker and offline startup
- [Training - Best Practices](/training/module11-bonnes-pratiques)
