.server modules
On this page

.server modules

Summary

Server-only modules that are excluded from client bundles and only run on the server.

// This would expose secrets on the client if not exported from a server-only module
export const JWT_SECRET = process.env.JWT_SECRET;

export function validateToken(token: string) {
  // Server-only authentication logic
}

.server modules are a good way to explicitly mark entire modules as server-only. The build will fail if any code in a .server file or .server directory accidentally ends up in the client module graph.

Route modules should not be marked as .server or .client as they have special handling and need to be referenced in both server and client module graphs. Attempting to do so will cause build errors.

If you need more sophisticated control over what is included in the client/server bundles, check out the vite-env-only plugin.

Usage Patterns

Individual Files

Mark individual files as server-only by adding .server to the filename:

app/
โ”œโ”€โ”€ auth.server.ts         ๐Ÿ‘ˆ server-only file
โ”œโ”€โ”€ database.server.ts
โ”œโ”€โ”€ email.server.ts
โ””โ”€โ”€ root.tsx

Server Directories

Mark entire directories as server-only by using .server in the directory name:

app/
โ”œโ”€โ”€ .server/               ๐Ÿ‘ˆ entire directory is server-only
โ”‚   โ”œโ”€โ”€ auth.ts
โ”‚   โ”œโ”€โ”€ database.ts
โ”‚   โ””โ”€โ”€ email.ts
โ”œโ”€โ”€ components/
โ””โ”€โ”€ root.tsx

Examples

Database Connection

import { PrismaClient } from "@prisma/client";

// This would expose database credentials on the client
const db = new PrismaClient({
  datasources: {
    db: {
      url: process.env.DATABASE_URL,
    },
  },
});

export { db };

Authentication Utilities

import jwt from "jsonwebtoken";
import bcrypt from "bcryptjs";

const JWT_SECRET = process.env.JWT_SECRET!;

export function hashPassword(password: string) {
  return bcrypt.hash(password, 10);
}

export function verifyPassword(
  password: string,
  hash: string
) {
  return bcrypt.compare(password, hash);
}

export function createToken(userId: string) {
  return jwt.sign({ userId }, JWT_SECRET, {
    expiresIn: "7d",
  });
}

export function verifyToken(token: string) {
  return jwt.verify(token, JWT_SECRET) as {
    userId: string;
  };
}

Using Server Modules

import type { ActionFunctionArgs } from "react-router";
import { redirect } from "react-router";
import {
  hashPassword,
  createToken,
} from "../utils/auth.server";
import { db } from "../utils/db.server";

export async function action({
  request,
}: ActionFunctionArgs) {
  const formData = await request.formData();
  const email = formData.get("email") as string;
  const password = formData.get("password") as string;

  // Server-only operations
  const hashedPassword = await hashPassword(password);
  const user = await db.user.create({
    data: { email, password: hashedPassword },
  });

  const token = createToken(user.id);

  return redirect("/dashboard", {
    headers: {
      "Set-Cookie": `token=${token}; HttpOnly; Secure; SameSite=Strict`,
    },
  });
}

export default function Login() {
  return (
    <form method="post">
      <input name="email" type="email" required />
      <input name="password" type="password" required />
      <button type="submit">Login</button>
    </form>
  );
}
Docs and examples CC 4.0
Edit