---
schema_version: "1"
id: 889f63d65c899fc87557deaefe3880a0973bfa84e068dd7b24e2c5e0311a360a
title: Server API Routes
collection: developer
version: current
locale: en-us
portal_url: https://docs.salesforce.com/developer/current/en-us/commerce-cloud/sfnext/guides/sfnext-server-routes.md
canonical_url: https://developer.salesforce.com/docs/commerce/pwa-kit-managed-runtime/guide/sfnext-server-routes.html
source_site: developer.salesforce.com
content_type: documentation
content_hash: 085cbd3474a712bc0461ffd7da1c596e1a066a3d14697330e2032e71aa9e11ac
products:
  - Composable Storefront
  - Salesforce APIs
---
# Server API Routes

Server API Routes, also known as Resource Routes, are the standard way to fetch and mutate data in Storefront Next. They implement the Backend-For-Frontend (BFF) pattern—your client-side components communicate with these server endpoints, which securely handle B2C Commerce API calls, authentication, and sensitive business logic.

## How It Works

All data fetching routes through server endpoints. Server routes handle:

- API authentication: SLAS flows, client secrets, and token management.
- B2C Commerce API proxying: All SCAPI calls route through server endpoints.
- Callbacks and redirects: Auth redirects, webhooks, JWKS proxying.
- Cookie and header management: Session handling, locale preferences.
- JSON responses: Data for `useFetcher()` or client-side `fetch()` calls.

This approach keeps credentials secure, reduces client bundle size, and centralizes data access patterns.

## The Backend-For-Frontend Pattern

Server routes implement the Backend-for-Frontend (BFF) pattern. Your storefront app acts as an intermediary between the client and backend services.

```
Browser  →  Server Route  →  B2C Commerce API
   ↑                              ↓
   └────── JSON Response ─────────┘
```

This architecture provides:

- API aggregation: Combine multiple Commerce API calls into single endpoints.
- Security: Keep API credentials and sensitive logic on the server.
- Performance: Reduce client-side JavaScript and network requests.
- Flexibility: Transform API responses to match your UI needs.

## Server Route Architecture

Storefront Next uses React Router 7 in framework mode. Routes are defined by files in `src/routes/` by using the flat routes convention.

A route module becomes a **resource route** (API endpoint) when it exports only data functions such as `loader` and `action`, without a default React component.

### Key Concepts

| Term           | Definition                                                              |
| -------------- | ----------------------------------------------------------------------- |
| Server route   | Any route file in `src/routes/` that runs on the server.                |
| Resource route | A server route that exports only data functions (no default component). |
| Loader         | A function that handles GET requests and returns data.                  |
| Action         | A function that handles mutations (POST, PUT, DELETE).                  |

### Naming Conventions

Storefront Next uses these naming conventions.

- `resource.*` files map to `/resource/...` endpoints.
- `action.*` files map to `/action/...` endpoints.
- `loader.*` files map to `/loader/...` endpoints.
- `_empty.*` files bypass parent layouts. Use this prefix when a route needs to render without the standard page wrapper, for example, the logout action that redirects immediately.

## Server-Side Data Layer

All data fetching in Storefront Next happens on the server. Resource routes use `loader` and `action` functions that run server-side for both initial page loads and client-side navigation.

This server-side approach provides:

- Security: Sensitive API keys and business logic stay on the server.
- Performance: Reduced client bundle size.
- Consistency: Single source of truth for data-fetching logic.
- SEO: All data is available during server-side rendering.

**important:** Always use server-side `loader` and `action` functions. While React Router supports `clientLoader` and `clientAction`, Storefront Next enforces a server-only data retrieval. Client-side loaders expose your app to security risks and violates the architecture. If you have a rare edge case that seems to require client-side data fetching, consult with the team before implementing.

## Route Types

Server routes support three main patterns: loaders for GET requests, actions for mutations, and dynamic parameters for flexible endpoints.

### Loader Routes (GET)

Use a `loader` to respond to GET requests. This example shows a store search endpoint.

```typescript
// src/extensions/store-locator/routes/resource.stores.ts
import { data, type LoaderFunctionArgs } from 'react-router'
import { extractResponseError } from '@/lib/utils'
import { createApiClients } from '@/lib/api-clients'

export async function loader({ request, context }: LoaderFunctionArgs) {
    try {
        const url = new URL(request.url)
        const postalCode = url.searchParams.get('postalCode') ?? undefined
        const maxDistance = url.searchParams.get('maxDistance')
        const distanceUnit = url.searchParams.get('distanceUnit') ?? 'km'

        const clients = createApiClients(context)

        const { data: stores } = await clients.shopperStores.searchStores({
            params: {
                query: {
                    postalCode,
                    maxDistance: maxDistance ? Number(maxDistance) : undefined,
                    distanceUnit: distanceUnit as 'mi' | 'km',
                },
            },
        })

        return Response.json({ success: true, stores })
    } catch (error) {
        const { responseMessage, status_code } = await extractResponseError(error as Error)
        return data(
            { success: false, error: responseMessage },
            { status: Number(status_code) }
        )
    }
}
```

### Action Routes (POST/PUT/DELETE)

Use an `action` to handle mutations and form submissions. This example shows a locale-setting endpoint.

```typescript
// src/routes/action.set-locale.ts
import { data, type ActionFunction } from 'react-router'
import { localeCookie } from '@/middlewares/i18next.server'

export const action: ActionFunction = async ({ request }) => {
    const formData = await request.formData()
    const locale = formData.get('locale') as string

    if (!locale) {
        throw new Response('Locale is required', { status: 400 })
    }

    const cookieHeader = await localeCookie.serialize(locale)

    return data(
        { success: true },
        {
            headers: {
                'Set-Cookie': cookieHeader,
            },
        }
    )
}
```

### Dynamic Route Parameters

Use dynamic segments when a family of endpoints shares a common shape.

```typescript
// src/routes/resource.auth.$operation.ts
import type { ActionFunctionArgs } from "react-router";

type AuthHandler = (request: Request, context: ActionFunctionArgs["context"]) => Promise<unknown>;

const authHandlers: Record<string, AuthHandler> = {
  "refresh-token": handleRefreshToken,
  "login-guest": handleLoginGuest,
  "login-registered": handleLoginRegistered,
};

export async function action({ request, params, context }: ActionFunctionArgs) {
  const operation = params.operation as string;

  const handler = authHandlers[operation];
  if (!handler) {
    return Response.json({
      success: false,
      error: `Unknown auth operation: ${operation}`,
    });
  }

  try {
    const result = await handler(request, context);
    return Response.json({ success: true, data: result });
  } catch (error) {
    return Response.json({ success: false, error: "Operation failed" });
  }
}
```

This dynamic segment maps to `/resource/auth/:operation`. For example, `/resource/auth/login-guest`.

### Calling B2C Commerce APIs with `useScapiFetcher`

For standard B2C Commerce operations, use the `useScapiFetcher` hook. This hook pairs with a generic server route so that you can call any SCAPI method from your components while keeping the actual API communication on the server.

You don't interact with the underlying server route directly—the hook handles everything for you.

```typescript
import { useScapiFetcher } from "@/hooks/use-scapi-fetcher";
import { useEffect } from "react";

export function ProductDetails({ productId }: { productId: string }) {
  // The hook expects: (clientName, methodName, options)
  // Options use the SCAPI structure: { params: { path: {...}, query: {...} }, body: {...} }
  const fetcher = useScapiFetcher("shopperProducts", "getProduct", {
    params: {
      path: { productId },
    },
  });

  useEffect(() => {
    fetcher.load();
  }, [productId]);

  if (fetcher.state === "loading") return <div>Loading...</div>;
  if (!fetcher.success) return <div>Error: {fetcher.errors?.join(", ")}</div>;
  if (!fetcher.data) return null;

  return (
    <div>
      <h1>{fetcher.data.name}</h1>
      <span>{fetcher.data.price}</span>
    </div>
  );
}
```

For mutations, use the `submit` method.

```typescript
import { useScapiFetcher } from "@/hooks/use-scapi-fetcher";

export function UpdateProfile({ customerId }: { customerId: string }) {
  const fetcher = useScapiFetcher("shopperCustomers", "updateCustomer", {
    params: {
      path: { customerId },
    },
    body: {},
  });

  const handleSubmit = (formData: FormData) => {
    fetcher.submit(formData);
  };

  return (
    <form
      onSubmit={(e) => {
        e.preventDefault();
        handleSubmit(new FormData(e.currentTarget));
      }}
    >
      <input name="firstName" placeholder="First Name" />
      <input name="lastName" placeholder="Last Name" />
      <button type="submit" disabled={fetcher.state === "submitting"}>
        {fetcher.state === "submitting" ? "Saving..." : "Save"}
      </button>
    </form>
  );
}
```

**note:** While `useScapiFetcher` looks like a direct API call, it serializes your request, sends it to the server route, executes the SDK method on the server, and returns the result. The hook provides these convenience properties: `data` (the response payload), `errors` (any error messages), `success` (a boolean), and `state` (`loading`, `submitting`, or `idle`).

**tip:** Don't `await` the `load()` or `submit()` methods expecting `data` to be populated when the promise resolves. These methods trigger the fetch but return immediately. Use the `state` property or `useEffect` to react to data changes instead.

## Request and Response Patterns

Server routes use standard Web APIs, such as [Request](https://developer.mozilla.org/en-US/docs/Web/API/Request), [Response](https://developer.mozilla.org/en-US/docs/Web/API/Response), and [FormData](https://developer.mozilla.org/en-US/docs/Web/API/FormData), for handling requests and responses. For detailed patterns, see the [React Router Actions](https://reactrouter.com/start/framework/actions).

## Error Handling

Handle errors gracefully and return appropriate status codes. Don't expose sensitive information in error messages.

```typescript
import type { ActionFunctionArgs } from "react-router";
import { data } from "react-router";
import { extractResponseError } from "@/lib/utils";

export async function action({ request }: ActionFunctionArgs) {
  try {
    const formData = await request.formData();
    const result = await performOperation(formData);

    return Response.json({ success: true, data: result });
  } catch (error) {
    // Use extractResponseError to safely extract error details
    const { responseMessage, status_code } = await extractResponseError(error as Error);

    return data({ success: false, error: responseMessage }, { status: Number(status_code) || 500 });
  }
}
```

**Status Code Guidelines**

| Status | Use Case                                        |
| ------ | ----------------------------------------------- |
| `400`  | Validation errors for missing or invalid input. |
| `401`  | Authentication required.                        |
| `403`  | Authorization denied.                           |
| `404`  | Unknown operations or resources.                |
| `500`  | Unexpected server failures.                     |

## Call Server Routes from UI Code

Use React Router's `useFetcher()` hook to call server routes without triggering a full page navigation. For complete documentation, see [React Router's Data Loading](https://reactrouter.com/start/framework/data-loading#usefetcher).

```typescript
import { useFetcher } from "react-router";

function LocaleSwitcher() {
  const fetcher = useFetcher();

  return (
    <fetcher.Form method="post" action="/action/set-locale">
      <select name="locale">
        <option value="en-US">English</option>
        <option value="fr-FR">Français</option>
      </select>
      <button type="submit">Change Language</button>
    </fetcher.Form>
  );
}
```

**tip:** Don't `await` the `fetcher.load()` or `fetcher.submit()` methods expecting `fetcher.data` to be populated when the promise resolves. These methods trigger the request but return immediately. Use `fetcher.state` or a `useEffect` watching `fetcher.data` to react when data arrives.

## Security Best Practices

Server routes handle sensitive operations. Follow these practices.

### Validate Input

Always validate and sanitize input from requests.

```typescript
export async function action({ request }: ActionFunctionArgs) {
  const formData = await request.formData();
  const email = formData.get("email");

  if (!email || typeof email !== "string") {
    return Response.json({ error: "Valid email address required" }, { status: 400 });
  }

  // Proceed with validated input
}
```

### Keep Secrets Server-Side

Never embed client secrets in browser code. Server routes exist specifically to keep credentials secure.

```typescript
// ✅ Correct: Secret stays on server
export async function action({ context }: ActionFunctionArgs) {
  const clientSecret = process.env.COMMERCE_API_SLAS_SECRET;
  // Use secret in server-side API call
}
```

### Avoid Open Proxies

Use the generic Commerce SDK proxy (`resource.api.client.$resource.ts`) carefully. If you implement similar patterns, enforce an allowlist of methods and validate parameters. Don't expose a "call-anything" proxy without guardrails.

### Set Cache Headers Intentionally

Public endpoints must set appropriate `Cache-Control` headers.

```typescript
return data(
  { products },
  {
    headers: {
      "Cache-Control": "public, max-age=300", // Cache for 5 minutes
    },
  },
);
```

## File Naming Conventions

| File Name                          | Route URL                        | Purpose                        |
| ---------------------------------- | -------------------------------- | ------------------------------ |
| `resource.stores.ts`               | `/resource/stores`               | Store locator BFF endpoint     |
| `resource.auth.$operation.ts`      | `/resource/auth/:operation`      | Authentication operations      |
| `resource.api.client.$resource.ts` | `/resource/api/client/:resource` | Generic Commerce SDK proxy     |
| `action.set-locale.ts`             | `/action/set-locale`             | Set locale cookie server-side  |
| `action.set-currency.ts`           | `/action/set-currency`           | Set currency preference        |
| `action.place-order.ts`            | `/action/place-order`            | Order placement                |
| `loader.wishlist-products.tsx`     | `/loader/wishlist-products`      | Wishlist products data         |
| `oauth2.jwks.ts`                   | `/oauth2/jwks`                   | JWKS proxy endpoint            |
| `_empty.logout.ts`                 | `/logout`                        | Logout endpoint without layout |

## Dependencies

| Package                               | Purpose                              |
| ------------------------------------- | ------------------------------------ |
| `react-router`                        | Framework with loader/action pattern |
| `@salesforce/storefront-next-runtime` | Runtime utilities, SCAPI types       |

## See Also

- [Salesforce B2C Commerce API (SCAPI) Client](https://developer.salesforce.com/docs/commerce/pwa-kit-managed-runtime/guide/sfnext-api-integration.md)
