Skip to content

Routing SSR And Server APIs

Chris Michael edited this page Jul 18, 2026 · 28 revisions

Effuse

Routing, SSR, And Server APIs

Status: Client routing and layer server APIs are current. A unified route manifest is planned in #218.

Client Router

const routes = defineRoutes([
  { path: '/', name: 'home', component: HomePage },
  { path: '/users/[id]', name: 'user', component: UserPage },
  { path: '/docs/[...slug]', name: 'docs', component: DocsPage },
  { path: '/shop/[[...slug]]', name: 'shop', component: ShopPage },
] as const);

const router = createRouter({
  history: createWebHistory(),
  routes,
});

installRouter(router);

Supported path forms include:

Form Meaning
:id Colon-style dynamic segment.
[id] Bracket dynamic segment.
[...slug] Required catch-all.
[[...slug]] Optional catch-all.
(group) Organizational segment omitted from the URL.

Route groups remain available in normalized route metadata for architecture and policy decisions. Matching uses deterministic specificity and validates conflicting normalized paths.

Navigation

Use Link for declarative navigation and RouterView for the matched route component. useRoute() exposes path, full path, params, query, hash, matched records, name, groups, pattern, and merged metadata. useRouter() exposes navigation, resolution, guards, and dynamic route operations.

Nested Routes

A parent component that owns child routes must render a nested RouterView. Parent and child components have independent lifecycle and route identity.

Layer-Owned Server APIs

const UserLayer = defineLayer({
  name: 'users',
  services: {
    users: () => ({ find: (id: string) => ({ id, name: 'Chris' }) }),
  },
  server: {
    api: {
      '/api/users/[id]': {
        GET: ({ params, services }) => services.users.find(params.id),
      },
    },
    actions: {
      refreshUser: ({ services }) => services.users.find('u1'),
    },
  },
});

Server routes are matched before SSR fallback. Handlers receive route params, query/body helpers, services, validation, response helpers, and request context.

File-Derived Server Endpoints

fromServerFiles converts an imported file map into layer server configuration. Default roots include API directories and action directories such as src/server/actions, app/actions, and src/actions. Files can export HTTP methods, default handlers, named actions, middleware, validation, and metadata.

import type { ServerApiFileModule, ServerActionFileModule } from '@effuse/core';
import { defineLayer, fromServerFiles } from '@effuse/core';

const files = import.meta.glob<
  ServerApiFileModule | ServerActionFileModule
>(['/src/server/api/**/*.ts', '/src/server/actions/**/*.ts'], { eager: true });

export const AppServerLayer = defineLayer({
  name: 'app-server',
  server: fromServerFiles(files),
});

A file at src/server/api/users/[id]/route.ts can export method handlers:

export const GET = ({ params }) => ({ id: params.id });

export const POST = async ({ json, params }) => ({
  id: params.id,
  input: await json(),
});

The same adapter recognizes Next-style app/api and app/actions roots. Route groups are removed from URLs while bracket params retain their runtime names.

This is an adapter into the layer server model, not a second runtime.

Validation

Handlers can validate JSON, form data, headers, params, query values, or an arbitrary value. Validators may be functions or objects with parse or safeParse methods.

const parseUser = (value: unknown): { name: string } => {
  if (!value || typeof value !== 'object' || !('name' in value)) {
    throw new Error('name is required');
  }
  return { name: String(value.name) };
};

const createUser = async ({ validate }) => {
  const input = await validate.json(parseUser);
  return { id: crypto.randomUUID(), ...input };
};

Validation failures become a 400 response with the stable EFFUSE_VALIDATION_FAILED code, source, message, and normalized issues.

SSR

createServerApp and createHandler build a request handler from a root component and layer graph. The SSR runtime supports rendered HTML, head collection, hydration data, asset manifests, API/action dispatch, and cleanup. createStreamingHandler exists as an experimental streaming path.

Metadata

Head and SEO primitives exist, including useSeoMeta, server SEO collection, head merging, Open Graph, Twitter metadata, links, and scripts. Full route-tree metadata generation and merge policy remain part of #218.

Related

Clone this wiki locally