Runtime
Lightweight frontend runtime for SPA applications without frameworks
Built for developers who want control without framework overhead
The runtime provides a lightweight foundation for building modern single-page applications without relying on large frontend frameworks. It is designed around a modular architecture that separates concerns such as dependency injection, routing, reactivity, and rendering into independent but cohesive parts.
Developers can build fully functional applications using a minimal set of primitives while maintaining full control over application structure and behavior. The runtime includes a strict dependency injection system, a client-side router with navigation guards, and a reactive state model powered by Proxy. Components follow a predictable lifecycle, ensuring consistent initialization, rendering, and cleanup across the application.
Template bindings allow declarative DOM updates using simple data attributes, reducing the need for manual DOM manipulation. The runtime is framework-independent and has zero external dependencies, making it suitable for long-term maintenance and custom architectures. It integrates seamlessly with the build system and templates, enabling a smooth development experience from project creation to deployment. This approach allows you to build scalable applications with clear architecture, without sacrificing performance or flexibility.
Quick Start
import { bootstrapApplication, provideRouter } from '@razerspine/runtime';
bootstrapApplication({
providers: [
provideRouter([
{ path: '/', component: HomePage }
])
]
});Core Features
DI Container
Strict dependency injection system
Router
SPA navigation with guards
Reactivity
Proxy-based reactive state
Components
Lifecycle-driven architecture
Component Example
export class HomePage extends BaseComponent {
increment() {
this.setState({ count: this.state.count + 1 });
}
}Architecture
core → router → reactivity → view → http → platformCommon Mistakes & Anti-patterns
Common pitfalls when working with the runtime and how to avoid them
❌ Injecting service without registering it
✔ Always provide services in bootstrapApplication()
❌ Expecting automatic instantiation
✔ Runtime uses strict DI mode (no auto new)
❌ Circular dependencies between services
✔ Keep services isolated and avoid cross-dependency loopsThe DI container works in strict mode. Services are not auto-created and must be explicitly registered during bootstrap. If a service is missing, the runtime will throw an error instead of silently failing, making configuration issues easier to detect.
❌ Direct state mutation outside of setState
✔ Always use setState() for predictable updates
❌ Replacing state object entirely
✔ State is Proxy-based and must remain stable
❌ Mutating deeply without understanding reactivity
✔ Use structured updates to keep UI in syncThe reactive system is based on Proxy. Direct mutation may work, but breaks predictable update flow. setState() ensures controlled updates and keeps rendering consistent.
❌ Performing async logic inside render()
✔ Use onInit() for async operations
❌ Forgetting cleanup for manual subscriptions
✔ Use onDestroy() for cleanup
❌ Manipulating DOM outside render()
✔ Let bindings handle DOM updatesThe lifecycle is strictly ordered: render → bind events → update → onInit. Async logic should be placed in onInit() to avoid inconsistent rendering behavior.
❌ Calling Router before bootstrap completes
✔ Router is initialized inside bootstrapApplication
❌ Ignoring guard return types
✔ Guards must return true | false | redirect string
❌ Assuming navigation errors will crash app
✔ Router handles errors internallyThe Router is designed to be fault-tolerant. Navigation errors are caught internally and do not break the application lifecycle. Optional error handlers can be attached for custom UI behavior.
❌ Forgetting to call applyBindings()
✔ Must be called manually after state updates
❌ Using bindForms without applyBindings
✔ They work together as a pair
❌ Not cleaning up listeners
✔ Always remove listeners to avoid memory leaksIn MPA mode, lifecycle is fully manual. You must initialize bindings, attach event handlers and handle cleanup yourself. Forgetting cleanup may lead to memory leaks or duplicated listeners.
❌ Relying on incorrect processing order
✔ data-for always runs before other bindings
❌ Mixing manual DOM updates with bindings
✔ Let applyBindings control DOM synchronization
❌ Misusing data-model on unsupported elements
✔ Use proper input types for two-way bindingThe bindings engine processes directives in a strict order. Structural changes like data-for must be applied first to ensure correct DOM updates. Mixing manual DOM manipulation with bindings may lead to inconsistent UI state.
Advanced Patterns & Best Practices
This section describes recommended architectural patterns and best practices for building scalable applications using the runtime. These patterns are based on real-world usage and internal runtime behavior.
Following these guidelines helps maintain predictable data flow, improves performance and prevents common architectural mistakes when working with reactive state, dependency injection and routing.
Keep Components Stateless Where Possible
Move business logic into services instead of components. Components should focus on rendering and user interaction, while services handle data and side effects.
Use DI for Shared Logic
Always register shared services in the DI container and access them via inject(). This ensures a single source of truth and avoids duplicated logic across components.
Avoid Direct State Mutation
Always use setState() instead of mutating state directly. This guarantees that the reactive system triggers DOM updates correctly.
Keep State Minimal
Store only the data required for rendering. Derived values should be computed inside methods instead of being stored in the reactive state.
Handle Async in onInit
Perform API calls and async operations inside onInit(). The lifecycle supports async execution and ensures proper rendering flow.
Clean Up Side Effects
Always clean up intervals, subscriptions or manual listeners inside onDestroy() to prevent memory leaks, especially in SPA navigation.
Use Router for Navigation
Avoid manipulating window.location manually. Use Router.navigate() to keep navigation consistent and lifecycle-safe.
Split Large Features
Break large pages into smaller logical units and services. This improves maintainability and aligns with the modular runtime architecture.