The SSR context problem

This is advanced documentation. You may need experience with Telefunc (or RPC) before being able to fully internalize the problem described here.

You likely don't need to read this; skimming through Guides > Server-Side Rendering (SSR) is usually enough.

Server-side calls VS browser-side calls

We usually call telefunctions remotely from the browser:

// CreateTodo.telefunc.js
// Environment: Node.js server

import { shield } from 'telefunc'

shield(onNewTodo, [shield.type.string])
export async function onNewTodo(text) {
  const todoItem = new Todo({ text })
  await todoItem.save()
}
// CreateTodo.jsx
// Environment: Browser

// As usual, the Telefunc transformer replaces this import with a thin HTTP client
import { onNewTodo } from './CreateTodo.telefunc.js'

async function onClick(form) {
  const text = form.input.value
  // We call `onNewTodo()` remotely from the browser
  await onNewTodo({ text })
}

function CreateTodo() {
  return (
    <form>
      <input input="text"></input>
      <button onClick={onClick}>Add To-Do</button>
    </form>
  )
}

But, when we do Server-Side Rendering, we may call telefunctions directly on the server-side instead of doing a remote call from the browser:

// components/TodoList.telefunc.js
// Environment: Node.js server

export async function getTodoItems() {
  const todoItems = await Todo.findMany()
  return todoItems
}
// server/index.js
// Environment: Node.js (not browser!)

import { renderToHtml } from 'some-ui-framework' // React, Vue, ...
import { TodoList } from '../components/TodoList.js' // A `<TodoList />` component

// If we would be on the browser-side, the Telefunc transfomer would replace this import
// with a thin HTTP client. But because we are on the server-side, the Telefunc transformer
// doesn't touch this import and leaves it as it is. This means that calling `getTodoItems()`
// here will directly call the function `getTodoItems()` defined in `TodoList.telefunc.js`.
import { getTodoItems } from '../components/TodoList.telefunc'

// Upon HTTP GET `/todoList` requests, our server renders the `<TodoList />` component to HTML
app.get('/todoList', async (req, res) => {
  const todoItems = await getTodoItems() // Calling our telefunction on the server-side
  const viewHtml = await renderToHtml(TodoList, { todoItems })
  res.status(200).send(html`<!DOCTYPE html>
    <html>
      <head>
        <title>My to-do List</title>
      </head>
      <body>
        <div id="view">${viewHtml}</div>
      </body>
    </html>`
  )
})

The problem

As we have seen in the previous section, we can call our telefunctions as well on the server-side.

But things can get tricky if one of our telefunction uses getContext().

Let's use getContext() in the example of the previous section:

// components/TodoList.telefunc.js
// Environment: Node.js server

import { getContext } from 'telefunc'

export async function getTodoItems() {
  const { user } = getContext()
  const todoItems = await Todo.findMany({ authorId: user.id })
  return todoItems
}
// server/index.js
// Environment: Node.js (not browser!)

import { getTodoItems } from '../components/TodoList.telefunc'

import { renderToHtml } from 'some-ui-framework'
import { TodoList } from '../components/TodoList.js'

// The usual Telefunc middleware
app.all('/_telefunc', async (req, res) => {
  provideTelefuncContext({ user: req.user }) // See https://telefunc.com/auth
  const httpResponse = await telefunc({ url: req.url, method: req.method, body: req.body })
  res.status(httpResponse.statusCode).type(httpResponse.contentType).send(httpResponse.body)
})

// Our SSR middleware
app.get('/todoList', async (req, res) => {
  // We also need to call `provideTelefuncContext()` here, because the `provideTelefuncContext()`
  // in the Telefunc middleware above is only called when a telefunction is called remotely
  // from the browser.
  provideTelefuncContext({ user: req.user })
  const todoItems = await getTodoItems() // Calling our telefunction on the server-side
  const viewHtml = await renderToHtml(TodoList, { todoItems })
  res.status(200).send(html`<!DOCTYPE html>
    <html>
      <head>
        <title>${req.user.name}'s to-do List</title>
      </head>
      <body>
        <div id="view">${viewHtml}</div>
      </body>
    </html>`
  )
})

In this example, the solution is simple: we can simply call provideTelefuncContext() before our server-side telefunction call.

But, in a real application, this is usually cumbersome, and sometimes difficult to achieve. This is what we call the SSR context problem.

For a list of solutions, see Guides > Server-Side Rendering (SSR).