---
title: "Your First Workflow"
description: "Install the Workflow SDK, create a durable workflow with steps, and trigger it from a SvelteKit route handler."
canonical_url: "https://vercel.com/academy/svelte-on-vercel/durable-tasks"
md_url: "https://vercel.com/academy/svelte-on-vercel/durable-tasks.md"
docset_id: "vercel-academy"
doc_version: "1.0"
last_updated: "2026-10-02T17:46:31.480Z"
content_type: "lesson"
course: "svelte-on-vercel"
course_title: "Svelte on Vercel"
prerequisites:  []
---

<agent-instructions>
Vercel Academy — structured learning, not reference docs.
Lessons are sequenced.
Adapt commands to the human's actual environment (OS, package manager, shell, editor) — detect from project context or ask, don't assume.
The lesson shows one path; if the human's project diverges, adapt concepts to their setup.
Preserve the learning goal over literal steps.
Quizzes are pedagogical — engage, don't spoil.
Quiz answers are included for your reference.
</agent-instructions>

# Your First Workflow

# Your First Workflow

Your serverless function has the lifespan of a mayfly: it handles a request, sends a response, and stops. `waitUntil()` from `@vercel/functions` buys you some extra seconds, like a mayfly that found a really good energy drink. A 30-second weather API outage or a Vercel redeployment mid-evaluation can still interrupt the work.

The [Workflow SDK](https://workflow-sdk.dev) handles work that outlives a request. A workflow can pause, retry, and resume across server restarts, and each step has its own retry lifecycle. If a function stops mid-step, the platform resumes the run from its recorded state.

## Outcome

Install the Workflow SDK, create a durable workflow that evaluates ski alerts against live weather data, and trigger it from a route handler.

## Fast Track

1. Install `workflow` and add `workflowPlugin()` to your Vite config
2. Create a workflow file with `"use workflow"` and `"use step"` directives
3. Trigger it from a route handler with `start()`

## Workflows vs Steps

Two directives, two roles:

```
"use workflow"                      "use step"
┌─────────────────────┐            ┌─────────────────────┐
│ Orchestrator         │            │ Worker               │
│ Sandboxed            │            │ Full Node.js         │
│ Deterministic        │            │ Side effects OK      │
│ Controls flow        │            │ Auto-retries (3x)    │
│ Calls steps          │            │ Does the real work   │
└─────────────────────┘            └─────────────────────┘
```

The workflow function controls the sequence: loops, branches, and parallel work. Step functions handle side effects such as fetching data, calling APIs, and reading files. If a step fails, the Workflow SDK retries it automatically (3 times by default) without re-running completed steps.

Keep `fetchWeather` out of the workflow function because workflow functions are sandboxed for determinism and cannot access the network or file system. Use them to coordinate work, and put side effects in step functions.

## Hands-on Exercise 3.1

Set up the Workflow SDK and create your first workflow:

**Requirements:**

1. Install the `workflow` package
2. Add `workflowPlugin()` to `vite.config.ts`
3. Create `workflows/evaluate-alerts.ts` with a workflow function and a step function
4. Complete the route handler at `src/routes/api/workflow/+server.ts` to start the workflow
5. The step should group alerts by resort, fetch weather for each, and evaluate conditions

**Implementation hints:**

- The workflow file goes at the project root in a `workflows/` directory (Workflow SDK convention)
- Import `workflowPlugin` from `workflow/sveltekit` and add it to your Vite plugins array
- The workflow function uses `"use workflow"` as the first line. The step function uses `"use step"`
- Inside a step, use dynamic imports for `#lib` modules: `const { getResort } = await import('#lib/data/resorts.ts')`
- Use `start()` from `workflow/api` in the route handler. It returns a run object immediately without waiting for the workflow to complete
- `Object.groupBy()` handles alert grouping (Node 24 supports it natively)

\*\*Note: Fluid compute should be enabled\*\*

The Workflow SDK is designed to use Vercel's Fluid compute. Without it, each workflow resumption can trigger a cold start. New Vercel projects enable Fluid compute by default, but verify the setting before you deploy.

## Try It

1. **Install and configure:**

   ```bash
   npm install workflow
   ```

   Restart your dev server after updating `vite.config.ts`.

2. **Trigger the workflow:**

   ```bash
   $ curl -X POST http://localhost:5173/api/workflow \
     -H "Content-Type: application/json" \
     -d '{"alerts": [{"id": "test-1", "resortId": "mammoth", "condition": {"type": "conditions", "match": "powder"}, "originalQuery": "test", "createdAt": "2025-01-01", "triggered": false}]}'
   ```

   Expected response:

   ```json
   {
     "runId": "wf_abc123...",
     "status": "started"
   }
   ```

   The workflow runs in the background. The route handler returns immediately.

3. **Check server logs:**
   ```
   [Workflow] Complete { evaluated: 1, triggered: 0 }
   ```

4. **Inspect in the Workflow dashboard:**

   ```bash
   npx workflow web
   ```

   Open the dashboard and you'll see your workflow run with its step, inputs, outputs, and timing.

## Commit

```bash
git add -A
git commit -m "feat(workflow): add Workflow SDK alert evaluation"
git push
```

## Done-When

- [ ] `workflow` package is installed and `workflowPlugin()` is in `vite.config.ts`
- [ ] `workflows/evaluate-alerts.ts` exists with `"use workflow"` and `"use step"` directives
- [ ] `/api/workflow` route handler starts the workflow and returns a run ID
- [ ] Workflow evaluates alerts against live weather data
- [ ] `npx workflow web` shows the completed workflow run

## Solution

**1. Vite config:**

```typescript title="vite.config.ts" {5,10}
import adapter from '@sveltejs/adapter-vercel';
import tailwindcss from '@tailwindcss/vite';
import { sveltekit } from '@sveltejs/kit/vite';
import { defineConfig } from 'vite';
import { workflowPlugin } from 'workflow/sveltekit';

export default defineConfig({
  plugins: [
    tailwindcss(),
    workflowPlugin(),
    sveltekit({ adapter: adapter({ runtime: 'nodejs24.x' }) })
  ]
});
```

**2. Workflow file:**

```typescript title="workflows/evaluate-alerts.ts"
import type { Alert } from '#lib/schemas/alert.ts';

interface EvaluateInput {
  alerts: Alert[];
}

export default async function evaluateAlerts({ alerts }: EvaluateInput) {
  "use workflow";

  const results = await evaluateAllAlerts(alerts);

  console.log('[Workflow] Complete', {
    evaluated: results.length,
    triggered: results.filter((r) => r.triggered).length
  });

  return results;
}

async function evaluateAllAlerts(alerts: Alert[]) {
  "use step";

  const { getResort } = await import('#lib/data/resorts.ts');
  const { fetchWeather } = await import('#lib/services/weather.ts');
  const { evaluateCondition } = await import('#lib/services/alerts.ts');

  const alertsByResort = Object.groupBy(alerts, (a) => a.resortId);
  const results = [];

  for (const [resortId, resortAlerts] of Object.entries(alertsByResort)) {
    const resort = getResort(resortId);
    if (!resort) continue;

    const weather = await fetchWeather(resort);

    for (const alert of resortAlerts!) {
      const triggered = evaluateCondition(alert.condition, weather);
      results.push({
        alertId: alert.id,
        resortId,
        triggered
      });
    }
  }

  return results;
}
```

**3. Route handler:**

```typescript title="src/routes/api/workflow/+server.ts"
import { start } from 'workflow/api';
import evaluateAlerts from '../../../../workflows/evaluate-alerts';
import type { RequestHandler } from './$types';

export const POST: RequestHandler = async ({ request }) => {
  const { alerts } = await request.json();

  if (!alerts || !Array.isArray(alerts)) {
    return Response.json({ error: 'alerts array required' }, { status: 400 });
  }

  const run = await start(evaluateAlerts, [{ alerts }]);

  return Response.json({
    runId: run.runId,
    status: 'started'
  });
};
```

The `"use workflow"` directive marks `evaluateAlerts` as the orchestrator. It calls `evaluateAllAlerts`, a step function marked with `"use step"`, to fetch weather data and evaluate conditions. If the step fails, the platform retries it up to 3 times automatically.

`start()` enqueues the workflow and returns immediately without blocking the route handler. The workflow runs in the background, where it can pause, retry failed steps, and survive function restarts.

## Troubleshooting

\*\*Warning: Module not found errors for #lib imports\*\*

Step functions run in their own context. If `#lib` path aliases don't resolve, use the full relative path instead: `await import('../src/lib/data/resorts.ts')`. Declare `#lib/*` in `package.json`, include `.ts` or `.svelte` extensions, and keep `workflowPlugin()` before `sveltekit()` in your Vite config.

\*\*Warning: Workflow starts but step never completes\*\*

Check the dev server logs for errors inside the step function. Step failures are retried silently by default. Run `npx workflow web` to see the step status and any error messages. If the step failed 3 times and exhausted retries, you'll see it marked as failed in the dashboard.

## Advanced: How Durable Execution Works

When the Workflow SDK runs your code, it records each step's input and output in an event log. If the function restarts mid-execution, the SDK replays that log to reconstruct state without re-running completed steps. Workflow functions must therefore be deterministic: every replay must make the same decisions.

```
First run:
  evaluateAllAlerts(alerts) → runs step → records result ✓

Function restarts mid-workflow:
  evaluateAllAlerts(alerts) → replays recorded result (skip!) ✓
  ...continues with next steps
```

This is also why `Math.random()` and `Date.now()` are fixed during workflow replay. The SDK intercepts them to preserve determinism.


---

[Full course index](/academy/llms.txt) · [Sitemap](/academy/sitemap.md)
