Skip to main content

Someone wanted to learn this too, so Grasp built them a personal learning path.

You can keep exploring this one or create your own

Configuring Server and Client Entry Points and Environment Variables

Hello! Let's dive into the second lesson of our module on Tanstack Start.

In our previous lesson, we successfully initialized two Tanstack Start projects, one using Node.js and the other with Bun. We saw how the CLI scaffolds a complete application and noted the presence of two new files: entry-client.tsx and entry-server.tsx. These files are central to today's topic.

This lesson addresses a fundamental concept of any SSR framework: how code execution is split between the server and the client. Our learning outcome is to differentiate the server and client entry points and configure environment variables for each context.

We will cover:

  • The distinct roles of the server and client entry points in the request-response and hydration lifecycle.
  • The "isomorphic" nature of Tanstack Start and the challenges it presents.
  • How to use framework-provided functions like createServerFn to explicitly control where your code runs.
  • The correct way to manage environment variables, distinguishing between public client-side configuration and private server-side secrets.

Understanding this separation is crucial for building secure, performant, and correct full-stack applications.

1. The Two Entry Points: Server and Client

A Tanstack Start application lives in two distinct environments: the server (Node.js or Bun) and the client (the user's browser). The framework manages this duality using two separate entry point files.

The Server Entry Point (src/server.ts)

When a user first navigates to your site, the request hits your server. Tanstack Start begins its work in the server entry point. By default, you may not see this file, as the framework provides a standard one for you. However, you can create src/server.ts to customize its behavior.

This file is responsible for:

  • Receiving the incoming HTTP request.
  • Running server-side logic, such as authentication or data fetching.
  • Rendering your React application to an HTML string (Server-Side Rendering).
  • Sending the generated HTML back to the browser.

Server Entry Point | TanStack Start React Docs

The official documentation, titled 'Server Entry Point', explains the role and structure of src/server.ts. It's optional but provides a powerful hook for customization.

Please read the introductory section, 'Server Entry Point'. Focus on understanding that this file is the entry point for all SSR-related work and handles incoming requests.

The Client Entry Point (src/entry-client.tsx)

Once the server-rendered HTML arrives in the browser, the client-side part of your application takes over. The src/entry-client.tsx file is the JavaScript entry point for the browser. Its primary job is hydration.

Hydration is the process where React "attaches" itself to the existing HTML structure sent from the server. It builds up its internal component tree and makes the static page fully interactive, attaching event listeners and enabling client-side state management without re-rendering the entire page from scratch.

In summary:

  • server.ts: Handles the initial request, renders the app to HTML on the server.
  • entry-client.tsx: Takes over in the browser, making the static HTML interactive.

2. The Isomorphic Challenge and Server Functions

Tanstack Start is, by default, isomorphic. This means the same code (e.g., your route components) is intended to run on both the server (during the initial render) and the client (during subsequent client-side navigation). This creates a challenge: what if a piece of code only works on the server, like accessing a database or using a secret API key?

The following video from Appwrite provides a crystal-clear explanation of this problem and introduces Tanstack Start's elegant solution: server functions.

Tanstack Start finally makes sense

This video, 'Tanstack Start finally makes sense', clearly explains the problem of running server-side code in an isomorphic framework and how server functions solve it.

Please watch the section from about a minute in up to three minutes. Pay close attention to: The Isomorphic Problem: Understand why code that works on the initial server load can fail during client-side navigation. Introducing Server Functions: Grasp the concept of a server function as a piece of code that always executes on the server, no matter where it's called from. Implementation: See how createServerFn is used to wrap server-only logic.

As the video shows, wrapping your server logic in createServerFn from @tanstack/react-start ensures it will always execute on the server. If a component running on the client calls this function, Tanstack Start automatically creates an RPC (Remote Procedure Call) behind the scenes—essentially a private API endpoint—to execute the function on the server and return the result.

3. Controlling Execution Context

Tanstack Start provides a suite of functions to give you precise control over where your code runs.

Getting Started with TanStack Start RC

The 'Getting Started with TanStack Start RC' video we saw in the last lesson also contains excellent, practical examples of these context-control functions.

Please watch the following segments: <span data-type="resource_video_timerange" data-resource-subitem-id="8cc39430" data-range-start="1466" data-range-end="1674">createServerFn</span>: This is a detailed look at createServerFn, showing how it's used for a form mutation that writes to a file on the server. This reinforces the concept from the previous video. <span data-type="resource_video_timerange" data-resource-subitem-id="068d6ac0" data-range-start="5377" data-range-end="5959">createServerOnlyFn</span>: This section introduces a stricter version for functions that should never be callable from the client (e.g., getting a database handle). Note the distinction: createServerFn can be called from the client (via RPC), while createServerOnlyFn will cause an error if imported into client code. <span data-type="resource_video_timerange" data-resource-subitem-id="26a0ed05" data-range-start="5959" data-range-end="6163">createClientOnlyFn</span> and <ClientOnly>: This demonstrates the reverse: how to ensure code that depends on browser APIs like localStorage only ever runs on the client to prevent server-side crashes.

Summary of Execution Controls:

Function/Component Purpose Use Case Example
createServerFn Creates a function that always runs on the server. Callable from the client (via RPC). Submitting a form, fetching data with a secret key.
createServerOnlyFn Creates a function that only runs on the server. Throws an error if imported into client code. A utility function to establish a database connection.
<ClientOnly> component A wrapper component whose children are only rendered on the client. Wrapping a component that uses window or localStorage.
createClientOnlyFn Creates a function that only runs on the client. Throws an error if called on the server. A function that directly manipulates the DOM.

4. Configuring Contexts with Environment Variables

Now that we can separate server and client logic, we need a way to configure them. Environment variables are essential for storing secrets (like database URLs and API keys) and public configuration (like app titles or public API endpoints).

Since Tanstack Start uses Vite under the hood, it inherits Vite's powerful and secure system for handling environment variables.

Environment Variables | TanStack Start React Docs

The official documentation on 'Environment Variables' is the definitive guide to this topic. It clearly explains the security model and the necessary conventions.

Please read the document, focusing on these key sections: Quick Start: Note the fundamental difference between process.env for server variables and import.meta.env for client variables with the VITE_ prefix by reviewing the basic setup. Environment Variable Contexts: Understand context boundaries to see which variables are accessible in server functions versus client components. Environment File Setup: Learn about the loading order for environment files (.env.local, .env.development, etc.). Security Best Practices: This is the most critical part. Internalize the core security rule: Never expose secrets to the client by using the VITE_ prefix. Common Problems: Skim common troubleshooting scenarios to be aware of potential issues, like variables being undefined.

The Golden Rules of Environment Variables

From the documentation, two rules are paramount:

  1. Server-Side Secrets: Any variable that is a secret (API key, database password, JWT secret) should be defined in your .env file without a special prefix (e.g., DATABASE_URL=...). You can only access these in your server-side code (like inside a createServerFn) using process.env.DATABASE_URL. They are never, ever sent to the browser.

  2. Client-Side Public Config: Any variable that needs to be accessible in the browser must be prefixed with VITE_ (e.g., VITE_API_ENDPOINT=...). You access these in your client-side code using import.meta.env.VITE_API_ENDPOINT. These variables are public and will be embedded directly into the client JavaScript bundle.

5. Practical Exercise

Let's apply these concepts in one of the projects you created in the last lesson.

  1. Create a .env file: In the root of your tanstack-start-node or tanstack-start-bun project, create a file named .env.

  2. Add Variables: Add the following lines to your .env file.

    # A server-only secret
    API_SECRET="s3cr3t_k3y_fr0m_d0t_3nv"
    
    # A public, client-safe variable
    VITE_APP_NAME="My Awesome Tanstack App"
    
  3. Restart Your Dev Server: Whenever you change .env files, you must restart your development server (npm run dev or bun run dev) for the changes to take effect.

  4. Access Variables in a Route: Open an existing route file, for example src/routes/index.tsx, and modify it to use these variables.

    import { createFileRoute } from '@tanstack/react-router';
    import { createServerFn } from '@tanstack/react-start';
    
    // 1. Define a server function to access the secret
    const getSecretMessage = createServerFn('GET', async () => {
      // This code runs ONLY on the server
      console.log('Accessing secret on the server...');
      const secret = process.env.API_SECRET;
    
      // Notice we don't return the secret itself, but use it.
      if (secret === 's3cr3t_k3y_fr0m_d0t_3nv') {
        return 'Server successfully verified the secret key!';
      }
      return 'Server could not verify the secret.';
    });
    
    export const Route = createFileRoute('/')({
      // 2. Call the server function in the loader
      loader: async () => {
        const message = await getSecretMessage();
        return { message };
      },
      component: HomeComponent,
    });
    
    function HomeComponent() {
      const { message } = Route.useLoaderData();
    
      // 3. Try to access variables directly in the component
      
      // This will be undefined in the browser for security.
      const serverSecret = process.env.API_SECRET; 
      console.log('Attempting to access process.env.API_SECRET on client:', serverSecret);
    
      // This is the correct way to access a public variable on the client.
      const appName = import.meta.env.VITE_APP_NAME;
    
      return (
        <div className="p-2">
          <h1 className="text-2xl mb-4">{appName}</h1>
          <h3 className="text-lg">Welcome Home!</h3>
          <p className="mt-4">
            <strong>Message from server:</strong> {message}
          </p>
        </div>
      );
    }
    

When you run this code, check your browser's console and your terminal's console. You will see that process.env.API_SECRET is undefined on the client, but the server function was able to access it correctly, proving the security boundary is working. The VITE_APP_NAME will be correctly displayed.

Conclusion

This lesson covered the core architectural division in Tanstack Start. You now understand how the framework manages server and client execution contexts and how to configure them securely.

Key Takeaways:

  • Tanstack Start uses distinct server (server.ts) and client (entry-client.tsx) entry points to manage SSR and hydration.
  • You can control where code executes using functions like createServerFn (for server-only logic accessible via RPC) and components like <ClientOnly> (for browser-only rendering).
  • Server-only environment variables are accessed via process.env and are never exposed to the client.
  • Client-safe environment variables must be prefixed with VITE_ and are accessed via import.meta.env. They are considered public.

We've introduced createServerFn as a way to define server-only logic. In our next lesson, we will explore this powerful feature in greater depth, focusing on how to create type-safe server functions to execute server-only logic, including how to validate their inputs.

Can't find a good explanation? Sign up and we'll make it for you

Sign up