Ingest Ingest Composable Server/less IO Framework
GitHub

Build Sequence

Render Views

Use view routes when a route primarily exists to render a template. They help keep rendering code from being duplicated across handlers by giving routing and rendering one shared pattern.

Task GuideImplementation Steps

On This Page

Structural Map

  1. Example
  2. Response data for templates
  3. Conditional rendering in handlers
  4. Why use this
  5. Read next

Use view routes when a route primarily exists to render a template. They help keep rendering code from being duplicated across handlers by giving routing and rendering one shared pattern.

Example

TypeScript
app.view.render = async (filePath, props) => {
  return await renderTemplate(filePath, props);
};

app.view.engine = async (filePath, req, res) => {
  const html = await app.view.render(filePath, req.data());
  res.setHTML(html);
};

app.view.get('/profile', './views/profile.hbs');

This split is useful because render becomes the reusable primitive while engine becomes the route-facing adapter that writes to the response.

Response data for templates

res.data() is useful when a handler needs to pass view-only values into the template without mixing them into the real result set.

TypeScript
app.view.engine = async (filePath, req, res) => {
  const props = res.data();
  const html = await app.view.render(filePath, {
    ...req.data(),
    props
  });
  res.setHTML(html);
};

This keeps template data separate from the real result set. A handler can attach view-only values to res.data() while still returning the actual payload with setResults():

TypeScript
if (res.code === 200) {
  res.data.set('sessionId', 'abc123');
  res.data.set('sessionUser', 'John Doe');
}

res.setResults(results);

Conditional rendering in handlers

Because render is separate, you can render manually inside a route or event handler when the response depends on runtime conditions.

TypeScript
export default async function UserDetail({ req, res, ctx }) {
  if (req.data.has('id')) {
    const html = await ctx.view.render('./views/me.hbs', {
      id: req.data('id')
    });
    res.setHTML(html);
  }
}

If the handler sets a body itself, a matching app.view.get('/user/:id', './views/user.hbs') route will not also render on top of that response.

TypeScript
app.view.get('/user/:id', './views/user.hbs');

app.get('/user/:id', async ({ req, res, ctx }) => {
  if (req.data('id') === 'me') {
    const html = await ctx.view.render('./views/me.hbs', {
      id: req.data('id')
    });
    res.setHTML(html);
  }
});

Why use this

  • keep route lookup and view lookup connected
  • support server-rendered pages without writing a full action for simple pages
  • reuse rendering logic outside direct view routes