Skip to main content
@craft-ts
LearnLearn with EffectAI agentsGuideReference
@craft-ts/core@craft-ts/component@craft-ts/dev-tools@craft-ts/effect@craft-ts/style@craft-ts/style-testing@craft-ts/i18n@craft-ts/i18n-effect@craft-ts/mcp
ExamplesMigrationEffect compatibility & maturityAdopting CraftTS progressivelyPress kitRoadmapBacklog
Beta
  • Learn
  • Learn with Effect
  • AI agents
  • Guide
  • Reference
  • Packages
  • Resources
  • Guide overview
  • Create a project
i.Core concepts
  • The mental model
  • Which primitive should I use?
  • Anatomy of a primitive
  • Generators and yield*
  • Insertions
  • Typed insertion pipes
  • Exceptions as values
ii.Managing state
  • Local state
  • State machines
  • query
  • Mutations
  • queryParams
  • asyncProcess
  • Selecting
  • Reacting to mutations
  • Collections
  • Collection utilities
  • Persistence
  • GlobalPersisterHandler
  • Pagination placeholders
  • Schema validation
iii.Recommended approaches
  • Inject at the point of use
iv.Structuring the app
  • craftService
  • Service scopes
  • Shaping the public API
  • Abstract services
  • App start
  • Lazy services
  • Server functions
  • craftRegisterFor
  • Target wrapper
v.Routing & type-safe DI
  • Setup
  • CLI automation
  • ESLint rules
  • Route providers
  • Route guards
  • Route exception handling
  • Non-blocking navigation
  • Global error component
  • Route load errors
  • Scaling routes
vi.Components & templates
  • Components
  • Activating the style system
  • Typed styles
  • Defining a design system
  • Tokens and variables
  • Global foundation and fonts
  • Pseudo-elements and animations
  • Axes and the matrix
  • Context obligations
  • Testing visual states
  • Text contrast
  • Visual attestation
  • Template obligations
  • Typed CSS variables
  • Fine-grained reactivity
  • Progressive rendering
  • Directives and .pipe(...)
  • settledValue & pendingNode
  • Accessibility
  • Customization
  • Content projection
  • Styling a component
  • Template migrator
vii.Forms
  • Overview
  • Validators
  • Submitting
  • Nested forms
  • Exception handling
  • Complete examples
viii.Testing
  • Testing services
  • Testing components
  • Type-level tests
  • Browser boundaries
  • Page stability
  • Architecture rules
    • Overview
    • Declarative baseline
    • Unique identities
    • HTTP endpoint ownership
    • Computed purity
    • Dependency cycles
    • Mutation reactions
    • Route DI proofs
    • Route component files
    • Path boundaries
    • Exclusive links
    • Persisted identities
    • insertSelect keys
    • craftEffect and network
    • craftEffect and imperative sync
    • Interactive element names
    • Metric thresholds
    • Server-state loaders
    • Primitive loader requirements
  • Extensible architecture graph
  • Graph insights
  • Craft graph vs Nx
ix.Reactivity utilities
  • craftComputed
  • craftEffect
  • craftMethod
  • source$
  • on$
  • fromEventToSource$
  • sourceFromEvent
  • afterRecomputation
x.Internationalisation
  • Type-safe i18n
  • The catalogue
  • Tokens
  • The runtime
  • With Effect
xi.Deployment (experimental)
  • Overview
  • Manifest reference
  • Diagnostics
  • Providers
  • Alchemy provider
xii.Going further
  • SSR and hydration
  • Effect integration
  • Program operators
  • Pattern matching
  • Observability
  • AI agents
  • Temporal runtime
  1. Testing
  2. Architecture rules
  3. Route component files

Route page components each live in their own file#

assertRouteComponentsInSeparateFiles requires every component attached to a route through component, loadComponent or a lazy import() to live outside the route-definition file. It also rejects two different routed page components sharing one common component file:

ts
export function keepRouteComponentsInSeparateFiles(
  graph: ArchitectureGraph,
) {
  assertRouteComponentsInSeparateFiles(graph.graph);
}

What it prevents#

This keeps the page boundary explicit:

typescript
// pages.routes.ts
export const pagesRoutes = craftRoutes('pages', [
  {
    path: 'orders',
    loadComponent: () => import('./orders-page'),
  },
]);
typescript
// orders-page.ts
export const OrdersPage = craftComponent(/* ... */);

Putting both declarations in pages.routes.ts makes route files grow into feature modules. Putting OrdersPage and CustomersPage in one pages.components.ts file creates the same problem at the lazy boundary: the chunk is no longer organized around one page entry point.

The rule covers both eager route targets and lazy targets. A route collection may contain several routes, but each page component declaration still has its own source file. Child components rendered by a page are not route targets and are not restricted by this rule. Reusing the same component declaration from multiple routes is not treated as two page components.

The intended shape#

Keep the route tree responsible for navigation and loading:

typescript
// account.routes.ts
export const accountRoutes = craftRoutes('account', [
  {
    path: 'profile',
    loadComponent: ({ withRetry }) =>
      withRetry(import('./profile-page')).then((module) => module.ProfilePage),
  },
]);

Keep the page implementation in its own file, with its own component dependencies and tests. loadChildren remains the right choice when a whole route collection should be lazy, while the collection's page components still follow this file boundary.

Failure message#

The assertion reports the route, component and offending source file. Move each page component to its own sibling file, then keep the route's import literal so the router and bundler can discover the lazy boundary.

See also#

  • Routing setup

  • Scaling routes

  • Route DI proofs

←Route DI proofsPath boundaries→

On this page

  • What it prevents
  • The intended shape
  • Failure message
  • See also

@craft-ts · MIT licensed

GitHubnpm

Search