Core Concepts
EreoJS is built on a few fundamental concepts that work together to create a cohesive full-stack development experience. Understanding these concepts will help you build applications effectively.
Architecture Overview
┌─────────────────────────────────────────────────────────┐
│ Browser │
│ ┌─────────────┐ ┌─────────────┐ ┌─────────────────┐ │
│ │ Islands │ │ Navigation │ │ Form Actions │ │
│ │ (Hydrated) │ │ (SPA-like) │ │ (Progressive) │ │
│ └─────────────┘ └─────────────┘ └─────────────────┘ │
└─────────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────┐
│ EreoJS Server │
│ ┌─────────────┐ ┌─────────────┐ ┌─────────────────┐ │
│ │ Router │ │ Loaders │ │ Actions │ │
│ │ (File-based)│ │ (Data fetch)│ │ (Mutations) │ │
│ └─────────────┘ └─────────────┘ └─────────────────┘ │
│ │
│ ┌─────────────┐ ┌─────────────┐ ┌─────────────────┐ │
│ │ Middleware │ │ Cache │ │ Streaming │ │
│ └─────────────┘ └─────────────┘ └─────────────────┘ │
└─────────────────────────────────────────────────────────┘Core Principles
Web Standards First
EreoJS uses standard web APIs throughout:
- Request/Response - All data flows through standard HTTP primitives
- FormData - Forms use the platform's native form handling
- URLSearchParams - Query strings use standard parsing
- Headers - HTTP headers are manipulated with the Headers API
This means your knowledge transfers directly to other tools and platforms.
Progressive Enhancement
Applications work without JavaScript and enhance when it's available:
- HTML first - Server renders complete HTML
- Forms work - Standard forms submit without JS
- JS enhances - Client-side navigation, optimistic updates
- Islands hydrate - Only interactive components get JavaScript
Explicit Over Magic
EreoJS prefers explicit configuration over conventions that hide behavior:
- Caching is opt-in - You decide what gets cached
- Hydration is explicit - You mark what needs JavaScript
- Data loading is visible - Loaders are functions you write
The Request Lifecycle
Request → Router → Middleware → Loader → Render → Response
↓
Cache (if configured)- Request arrives at the Bun server
- Router matches the URL to a route file
- Middleware runs (auth, logging, etc.)
- Loader executes to fetch data (may use cache)
- Component renders with loader data
- Response streams HTML to the browser
For mutations:
Form Submit → Router → Middleware → Action → Revalidate → ResponseKey Concepts
Routing
File-based routing that maps your directory structure to URLs:
routes/
├── index.tsx → /
├── about.tsx → /about
├── posts/
│ ├── index.tsx → /posts
│ └── [id].tsx → /posts/:idData Loading
Loaders fetch data before rendering:
export const loader = createLoader(async ({ params }) => {
const post = await db.posts.find(params.id)
return { post }
})Actions handle form submissions:
export const action = createAction(async ({ request }) => {
const data = await request.formData()
await db.posts.create(data)
return redirect('/posts')
})Rendering Modes
Choose how each route renders:
- SSR - Server-side rendering (default)
- SSG - Static site generation at build time
- CSR - Client-side rendering
- Streaming - Progressive HTML streaming with Suspense
Islands Architecture
Selective hydration for interactive components. Add 'use client' to mark a component for client-side hydration:
// app/components/Counter.tsx
'use client';
import { useState } from 'react';
export function Counter() {
const [count, setCount] = useState(0);
return <button onClick={() => setCount(c => c + 1)}>{count}</button>;
}Only island components ship JavaScript — the rest of the page is static HTML.
Caching
Tag-based cache invalidation:
export const config = {
cache: {
maxAge: 3600,
tags: ['posts', `post-${id}`]
}
}
// Invalidate when needed
await revalidateTag('posts')Middleware
Request/response processing pipeline:
export const middleware = [
'auth', // Named middleware
'rateLimit',
async (request, context, next) => { // Inline function
console.log(request.url)
return next()
}
]Data Flow
┌──────────────────────────────────────────────────────────┐
│ Server │
│ │
│ loader() ──────────────────────────► { data } │
│ │ │ │
│ │ ▼ │
│ │ Component(data) │
│ │ │ │
│ ▼ ▼ │
│ Cache HTML Stream │
│ │
└──────────────────────────────────────────────────────────┘
│
▼
┌──────────────────────────────────────────────────────────┐
│ Browser │
│ │
│ HTML ──► Islands Hydrate ──► Interactive Components │
│ │
│ Form Submit ──► action() ──► Revalidate ──► Re-render │
│ │
└──────────────────────────────────────────────────────────┘Component Model
Route Components
Export a default function component:
// routes/posts/[id].tsx
export default function Post({ loaderData, params }) {
return <h1>{loaderData.post.title}</h1>
}Props available:
loaderData- Data from the loaderactionData- Result from the actionparams- URL parameterssearchParams- Query string parameters
Layout Components
Wrap child routes:
// routes/_layout.tsx
export default function Layout({ children }) {
return (
<div>
<nav>...</nav>
<main>{children}</main>
</div>
)
}Island Components
Interactive components that hydrate on the client. Mark them with 'use client':
// app/components/Counter.tsx
'use client';
import { useState } from 'react';
export function Counter({ initialCount = 0 }) {
const [count, setCount] = useState(initialCount);
return <button onClick={() => setCount(c => c + 1)}>{count}</button>;
}Use them directly in routes:
import { Counter } from '~/components/Counter';
export default function Home() {
return (
<div>
<h1>Static HTML</h1>
<Counter initialCount={0} /> {/* Only this hydrates */}
</div>
);
}Type Safety
EreoJS provides full TypeScript support:
import type { LoaderFunction, ActionFunction } from '@ereo/core'
interface Post {
id: string
title: string
}
export const loader: LoaderFunction<{ post: Post }> = async ({ params }) => {
const post = await getPost(params.id)
return { post }
}
export default function Post({ loaderData }: { loaderData: { post: Post } }) {
return <h1>{loaderData.post.title}</h1>
}Next Steps
Dive deeper into each concept:
- Routing - File-based routing patterns
- Data Loading - Loaders and actions
- Rendering Modes - SSR, SSG, streaming
- Islands - Selective hydration
- Caching - Cache strategies
- Middleware - Request processing
- State Management - Signals, stores, and React integration
- Forms - Form strategies and when to use each approach
- Type Safety - Type system and route type inference