SPA -> MPA
The goals of this project
1. Fast, responsive application
Load only the data needed for the page the user is viewing.
2. Modern navigation
This plan modernizes Vessel Vanguard into a fast, modular, enterprise-class application by replacing the SPA “dashboard with tabs” design with proper pages:
/vessels/:id/tasks
/vessels/:id/equipment
/vessels/:id/documents
etc.
3. Fleet-wide visibility
Add new “All” pages:
- All Tasks
- All Documents
- All Checklists
- All Logs
These allow fleet managers to see everything across vessels without clicking into each one.
4. Better user experience
Clean URLs, predictable navigation, and the ability to open modules in new tabs.
What this achieves
By moving away from the “load everything upfront” SPA model to lazy, page-specific data loading, the app will:
- Strong long-term maintainability for developers
- Load 4–10x faster upon login
- Clear and predictable navigation with dedicated pages for every module
- Fleet-wide oversight for managers allowing us to scale to fleets with hundreds of vessels
- Perform better on weak networks (marinas, boats, etc.)
- Use dramatically less memory
Expected Performance Improvements
Area
- Login → dashboard load
- Browser memory footprint
- Network load at login
- Fleet management UX
Before
- 8–40 secs
- Very high
- Huge global sync
- Per-vessel only
After
- 1–4 secs
- Moderate
- Minimal initial sync
- Global “All” tables
Improvement
- 4–10× faster
- 50–75% reduction
- 60–80% reduction
- Massively improved usability
Current Technical Architecture (diagnosing the problem)
The current routing/UI behavior is an SPA with inline expansions
Vessel Vanguard renders nearly everything inside a single “dashboard” route:
- src/App.tsx wires a single layout with nested routes under /vessel/:vesselid → VesselDashboard, with sub routes for maintenance, equipment, documents, checklists, logs, etc. That’s why the header/URL stays put while the center swaps content.
- Task rows open via an inline ‘expando’ (MaintenanceList → MaintenanceListRow → ExpandoRow), not a dedicated page.
Data loading pattern:
Upon sign-in:
- configureDataStore triggers (middleware in src/app/AmplifyMiddleware.tsx)
- It immediately calls configureObserveQueries
- This registers dozens of Amplify DataStore observeQuery subscriptions:
- tasks (safety + maintenance)
- procedure steps
- equipment
- components
- logs
- checklists
- documents
- media
- contacts
…all at once.
The task subscription pulls all maintenance/safety tasks for every vessel the user can access (filtered only by vesselId and a ~2‑year date window in syncExpressionsForNormalUser), then dispatches setTasks. Procedure steps, equipment, runtimes, components, etc. are also loaded globally for those vessels.
MaintenanceView/MaintenanceList just reads from Redux selectors; expanding a task uses already-synced data. There’s no “load details on demand” per task or per tab—the bulk of the data comes down up front during that initial sync.
4. The dashboard waits for this entire sync to finish.
Implications and speed options
- Even a simple user login forces a huge dataset sync. The UI feels slow because the SPA waits for these global queries.
- Simply linking tasks/equipment/documents to standalone pages won’t cut network load unless we also change the data strategy. To speed things up, narrow the sync/observe scope: subscribe only to the current vessel/module, trim the task date/status window further, and lazily start module subscriptions when their route is opened (plus code-split those routes). Without that, you’re always paying the cost of syncing everything on landing.
New Architecture (Modern, Fast, Scalable)
So the initial project is to decouple the modules into real pages and reducing initial data load. Here’s a concrete routing/UX plan to move modules out of the nested dashboard and into per-vessel pages.
New UX
- The “Vessels” landing shows all vessels vertically, each with the existing banner (hero image + quick stats + actions).
- Each module opens a dedicated page per vessel (full page load in the outlet), e.g. “Tasks” opens /vessels/:vesselId/tasks showing MaintenanceView for that vessel; same pattern for equipment, documents, checklists, logs.
- The banner on a module page shows the vessel info and module-specific actions; content below is only that module.
Routing changes
- Vessel list / fleet overview: Define top-level vessel module routes (no nesting under dashboard):
- /dashboard → new VesselList page with stacked banners and “Tasks / Equipment / Documents / Checklists / Logs” entry points.
2. Dedicated module pages per vessel: Define top-level vessel module routes with each module becoming a standalone page with its own route (no nesting under dashboard):
- /vessels/:vesselId/tasks → MaintenanceView (safety toggle as today, using query tab=… if needed).
- /vessels/:vesselId/equipment → existing EquipmentRouter root component.
- /vessels/:vesselId/documents → existing DocumentRouter root component.
- /vessels/:vesselId/checklists → ChecklistRouter.
- /vessels/:vesselId/logs → LogRouter.
3. Remove the nested module routes under /vessel/:vesselid. Keep /vessel/:vesselid only for a dashboard summary view, but modules no longer live under it.
5. Update navigation/links:
- Top nav entries point to /vessels for the list, and module links point to /vessels/:id/.
- Banner buttons on VesselList use those URLs.
Router wiring (App.tsx):
- Keep Layout and RequireAuth.
- Add a route group under /vessels:
- index → VesselList
- :vesselId/tasks → MaintenanceView
- :vesselId/equipment/* → EquipmentRouter(“equipment”, false) adjusted to accept :vesselId at root.
- :vesselId/documents/* → DocumentRouter(“documents”, false) adjusted similarly.
- :vesselId/checklists/* → ChecklistRouter(“checklists”, false)
- :vesselId/logs/* → LogRouter()
- Remove {MaintenanceRouter(“maintenance”, false)} etc. from the /vessel/:vesselid children.
Component tweaks:
- Ensure each module reads vesselId from useParams (already true for MaintenanceView); adjust routers that previously assumed a nested path to accept the top-level :vesselId.
- Add a shared VesselBanner component to reuse the hero/summary across VesselList cards and module pages.
DataStore consideration to be addressed separately
New fleet-wide “All” tables
- Add a horizontal topbar strip below the logo and above the vessel content with buttons/links: All Tasks, All Documents, All Checklists, All Logs (and any others you want).
- Clicking a button routes to a full-page table for that entity, not scoped to a single vessel.
- Include a vessel column so users can see origin and click back to a vessel-specific page.
- Add top-level routes (parallel to existing vessel routes):
- /all/tasks
- /all/documents
- /all/checklists
- /all/logs
- AllTasksPage
- AllDocumentsPage
- AllTasksPage AllChecklistsPage
- AllLogsPage
Data sourcing approach: lazy & modular
- Only load vessel list + minimal user profile on login.
- When user opens a module, load data for that vessel + that module only.
- Optionally prefetch data for commonly used modules in the background.
- Code-split modules so the JS bundle loads only what is needed.
Tasks: DataStore already syncs all tasks for the user’s accessible vessels into state.maintenance.tasks. Current selectors filter by vessel. Add a new selector to return all tasks, with optional filters (status, search, date range).
Documents: Use state.documents.files/folders (and/or state.media.media if documents are stored there). Add a selector that returns all document rows, enriched with vessel/organization metadata.
Checklists: Use state.checklists.tasks or equivalent; add a selector that returns all checklist tasks without vessel filtering.
Logs: Use state.logs.logs (and entries) with a selector that returns all logs, unfiltered by vessel.
Expected Improvement
- Initial load 4–10× faster
- Network cost drastically lower
- DataStore subscriptions reduced 60–80%
Implementation Plan
Phase 1 — Routing & UX foundations (1–2 weeks)
- Implement new top-level routing
- Build /vessels list page
- Extract banner into shared VesselBanner component
- Move each module to its own page
Outcome: Fleet managers can see everything across vessels.
Phase 2 — “All” fleet tables (1 week)
- Build:
/all/tasks
/all/documents
/all/checklists
/all/logs
- Add topbar with global menu
- Add filters + linking back to vessels
Outcome: New navigation structure working.
Phase 3 — DataStore Optimization (2 weeks)
- Reduce initial sync to:
- vessels
- user profile
- basic permissions
- Lazy load module data on demand
- Optional: Code splitting
Outcome: Login performance improves dramatically.
Phase 4 — Decommission Dashboard SPA (1 week)
- Remove outdated nested SPA routes
- Optional: Remove inline expandos
- Reduce bundle size
Outcome: Cleaner, smaller, more maintainable codebase.
Risks & Mitigation
Risk
- Inconsistent data loads across pages
- Old routes still referenced
- Users with slow connections
Mitigation
- Shared DataStore utilities + vesselId passed via URL
- Remove /vessel/:id/* children entirely
- Lazy loading + pagination in “All” tables