---
title: "Post-response Work"
description: "Schedule short server work with after() without delaying the response."
canonical_url: "https://mcp.farmjs.dev/docs/after"
markdown_url: "https://mcp.farmjs.dev/docs/after.md"
last_updated: "2026-10-20"
x_farming_labs_generated_preamble: true
---

# Post-response Work
URL: /docs/after
LLM index: /llms.txt
Description: Schedule short server work with after() without delaying the response.

# Post-response Work

Use `after()` for short server work that should start once Farm has finished the current response. Typical uses include analytics, audit events, cache warming, notifications, and non-critical cleanup.

Import it from the server-only entry:

```ts
import { after } from "@farm.js/core/after";
```

## Use it in a page

`after()` does not add its callback to the page's response time.

```tsx
import { after } from "@farm.js/core/after";
import { recordPageView } from "@/lib/analytics";

export default async function ProductPage({ params }) {
  const product = await getProduct(params.id);

  after(async () => {
    await recordPageView({ productId: product.id });
  });

  return <ProductDetails product={product} />;
}
```

Farm waits for a streamed response body to finish before starting the callback. Redirect and not-found responses use the same request lifecycle.
Cancelling a Web response also finishes that lifecycle, even if the stream producer's cleanup
is still pending. Farm starts `after()` callbacks and stops counting the response as active;
the caller of `cancel()` still receives the producer's cleanup promise, including any rejection.

## Use it in an API route

Keep work required for correctness in the handler. Schedule only the follow-up work that may happen after the client receives success.

```ts
import { after } from "@farm.js/core/after";
import { createEndpoint } from "@farm.js/core/api";
import { z } from "zod";

export const POST = createEndpoint(
  {
    method: "POST",
    body: z.object({ orderId: z.string() }),
  },
  async ({ body }) => {
    const order = await confirmOrder(body.orderId);

    after(async () => {
      await sendReceipt(order.id);
      await recordOrderAnalytics(order.id);
    });

    return { ok: true, order };
  },
);
```

The same API works in layouts, request middleware, server functions, and form actions because Farm creates one post-response queue for the whole request.

## Behavior

- `after(callback)` returns `void`; Farm owns when the callback starts.
- Callbacks start after the response finishes. For streaming responses, that means after the complete body is sent or the stream closes.
- Callbacks run one at a time in registration order.
- A callback may call `after()` again. The nested callback joins the end of the current queue.
- A callback error is logged, does not change the response, and does not prevent later callbacks from running.
- Request-local async context active at registration is preserved for the callback.
- Calling `after()` outside an active server request throws a clear runtime error.

On serverless adapters, Farm registers the queue with the provider's `waitUntil` lifecycle. On a Node server, Farm uses the response `finish` and `close` events. This keeps supported invocations alive without making the browser wait for the callback.
If the Node response has already ended or been destroyed when Farm's middleware starts, Farm
still completes the queue and removes its response listeners without waiting for another event.

## Best practices

Capture stable values before scheduling work:

```ts
const userId = session.user.id;
const eventId = crypto.randomUUID();

after(() => analytics.track({ eventId, userId, name: "checkout.completed" }));
```

Do authentication, authorization, validation, and the primary database write before returning the response. The response is already committed when an `after()` callback runs, so the callback cannot safely change its status, headers, or cookies.

Make callbacks idempotent when they call external services. A deployment runtime may stop unexpectedly, and `after()` does not add durable storage, retries, or exactly-once delivery.

Use [Cron](/docs/cron) for periodic route invocation. Use the [Jobs Integration](/docs/integrations/jobs) for durable retries, long-running work, queues, visibility, or recovery. Use `after()` for bounded request follow-up work where low latency matters more than durable execution.

## Sitemap

See the full [sitemap](/sitemap.md) for all pages.
Docs-scoped sitemap: [/docs/sitemap.md](/docs/sitemap.md).
Well-known sitemap: [/.well-known/sitemap.md](/.well-known/sitemap.md).
