---
title: Server Entry
sidebar:
  icon: server
description: Handle a request in your server entry, or skip it and let static files take over.
---

Every request hits this file first. The default path is [`src/server.ts`](/oxide/configuration#workerentry). Export a `fetch` that takes the request and returns a response. On [`preset: "celld"`](/oxide/configuration#preset) the runtime also passes Worker `env` and `ctx` — the same pair actions read with [`useEnv()` / `useFetchCtx()`](/oxide/server-actions#useenv-and-usefetchctx).

```ts twoslash src/server.ts
export default {
  fetch(
    request: Request,
    env?: { SECRET?: string },
    ctx?: { waitUntil?(p: Promise<unknown>): void },
  ) {
    if (new URL(request.url).pathname === "/api/ok") {
      ctx?.waitUntil?.(Promise.resolve());
      return new Response(env?.SECRET ?? "ok");
    }
  },
};
```

If `fetch` returns nothing, `oxidejs` keeps going. With a page in the project, that means a matching static file. Missing files fall back to `index.html` only for navigations (`Sec-Fetch-Dest: document` or `Accept: text/html`). A fetch for `/missing.js` is still `404`.

`public/` is copied next to the client on [`preset: "fetch"`](/oxide/configuration#preset). Hashed assets get `Cache-Control: immutable`. `index.html` is `no-cache`. Without a page, the build is only the server bundle - there is nothing to fall through to.

Named exports such as `scheduled` and `queue` stay on the celld wrapper. Spread them yourself if you replace `fetch`.

To serve typed RPC from this file, set up a tacho router and pass it to `handle()`. See the [Tacho quickstart](/tacho/quickstart).
