Feature Isolation and Portability

A core architectural feature of Baseplate is that it formalizes how application features are designed, developed and deployed to be a highly repeatable and portable process.  The goal of feature isolation is to make features that you build on Baseplate fully portable.  You should be able to drop a feature from one Baseplate app directly into another with minimal wiring.   This should occur while maintaining a shared look & feel, data access, and schema setup consistency.

Feature isolation means that features are isolated from one another and can be run and updated independently.   Feature dependencies can be declared for any cross feature access to data or functionality. 

The goal of feature portability is intended to create a Lego like system for building prototype applications.   When initially creating a Baseplate app you are able to import a variety of "starting" features that quickly implement needed, common functionality that is reused across many apps.  Feature isolation is intended to help you fight simple system and coding bloat: as functionality expands - across dashboards, analytics tools, admin panels, and workflows - the ability to manage and trace features becomes increasingly difficult. This leads to fragmented permissions, duplicated work, inconsistent onboarding, and opaque system behavior.  Feature isolation makes the maintenance and upgrading of discrete areas of system function easier.

Developers interact with the Feature Registry through a simple, structured JSON ile (featureRegistry.ts), which defines the basic high level information about the feature.   Features are create and deployed in the frontend under the (features)/feature_name directory.

Feature Folder Layout

Each feature lives in a dedicated directory under /frontend/src/app/(features).   Note that the parentheses around (features) allow us to use default React mounting for the features in the URLs.  Each feature defines a feature-slug which is the name of the directory the feature is stored in and the mounting point for the feature at your app mounting point.   For example the feature “voiceos” would mount at http://localhost/voiceos/ if you are developing locally and mounting the app at the root level of your server.

Here’s how a feature directory is laid out:

/frontend/src/app/(features)/
├── feature-slug/             # e.g., strategy-forge, content-forge, product-forge
│   ├── layout.tsx            # Symlink to ../../dashboard/layout.tsx
│   ├── page.tsx              # Main feature page (optional)
│   ├── README.md             # Feature documentation
│   ├── lib/                  # Feature-specific libraries
│   │   ├── types/            # TypeScript type definitions
│   │   │   ├── index.ts      # Barrel export
│   │   │   └── *.ts          # Feature-specific type files
│   │   ├── api/              # Supabase client wrappers
│   │   │   ├── index.ts      # Barrel export
│   │   │   └── *.ts          # API function files (one per entity)
│   │   ├── edge/             # Supabase edge functions
│   │   ├── sql/              # Database schema and migrations
│   │   │   └── [feature-slug]-setup.sql   # SQL setup for feature
│   │   └── components/       # Shared visual components
│   │       ├── index.ts      # Barrel export
│   │       └── *.tsx         # Feature-specific components
│   ├── [routing-group]/      # Next.js routing (optional)
│   │   └── page.tsx          # Route page
│   └── .../[id]/             # Dynamic routes
│       ├── page.tsx          # Detail page
│       └── edit/
│           └── page.tsx      # Edit page

Note that there is a layout.tsx in the “features” directory that is a symbolic link to the app standard layout in /frontend/src/app/dashboard/layout.tsx.   This ensures you inherit the standard application layout in your features.  You can override this with your own feature specific layout as and if needed.   If all features use the system standard layout you can promote this symlink up a directory.

Routing

We want Next.js to mount features at the root level of the application. In the file route conventions of React Router a directory or file name wrapped in parentheses indicates an optional route segment.  The directory (or file) with parentheses isn’t forced into the path. Instead, the segment becomes optional.  In practice that means you should be able to access your feature from two routes:

  • /feature-slug
  • /features/feature-slug

Packaging & Portability

Each feature’s index.ts should re-export all the elements needed for other portions of your application to access and update the feature.

// src/app/features/feature-slug/index.ts
export * from "./pages";
export * from "./lib/api";
export * from "./lib/types";
export * from "./lib/components";
export * from "./hooks";
export { default as FeatureSlugConfig } from "./config";

You can then import the feature as:

import { FeatureSlugConfig } from "@features/feature-slug";

Obviously if you only want to expose certain aspect of the feature you can limit the above export to the feature elements you want the rest of the app to "see".  To make these portable between Baseplate apps, use TypeScript project references:

Feature Registry

The file /frontend/src/app/features/registry.json provides a way to register features with the application.   Here’s how that is setup

{
 "$schema": "./feature-registry.schema.json",
 "features": [
   {
     "slug": "user-profiles",
     "enabled": true,
     "version": "1.2.0",
     "path": "/user-profiles",
     "displayName": "User Profiles",
     "description": "Manage user profile data, avatars, etc.",
     "dependencies": [],
     "order": 10
    },

   {
     "slug": "billing",
     "enabled": false,
     "version": "0.9.5",
     "path": "/billing",
     "displayName": "Billing / Invoicing",
     "description": "Subscription and billing UI for customers.",
     "dependencies": ["user-profiles"],
     "order": 20
   }
 ]
}


Here’s what the fields mean:

  • slug: Unique identifier of the feature (matches folder name under src/app/features/slug).
  • enabled: Boolean indicating whether this feature should be mounted/available in this build.
  • version: Version of the feature module (helps for portability and migrations).
  • path: The route path where the feature will be mounted (so router logic can map it).   Defaults to the feature slug.
  • displayName - The name that this feature should be mounted under in the menu
  • Description - The long form description of the feature
  • dependencies: Array of other feature slugs this feature needs (so you can enforce ordering or warn if missing).
  • order: Menu display or mount order for the feature
  • parentMenuName: Optional.   Name of a parent menu item to mount this feature under.