Recommended resources
The freely available resources this lesson will direct you to.
Hello! Let's continue our journey into Tanstack Start.
In our previous lesson, we learned how to create type-safe server functions using createServerFn. We established that these functions are the cornerstone for executing server-only logic, like accessing databases or using secret keys, and we touched upon the idea that a build process magically strips this sensitive code from the client bundle.
Today, we're going to pull back the curtain on that "magic." Our learning outcome is to analyze the production build output to verify that server functions are excluded from the client bundle. This isn't just an academic exercise; it's a crucial step in building trust in your framework and deeply understanding its security model. By the end of this lesson, you will be able to:
- Explain the compilation process that separates server and client code.
- Relate this mechanism to the broader context of modern web frameworks like React Server Components.
- Perform a hands-on analysis of a production build to find concrete evidence of this code stripping.
- Understand the performance trade-offs between bundle size and network payload.
This knowledge is fundamental to your goal of migrating a SPA, as it gives you the confidence to move logic to the server, knowing it will remain secure.
1. The Mechanism: How Server Code is Removed
The core of createServerFn's security lies in a build-time transformation process. It's not a runtime check; the separation happens before your code is ever deployed. Tanner Linsley, the creator of Tanstack, provides the clearest explanation of this process.
Building Better Apps with TanStack Start and Tanner Linsley
In this segment from an interview, Tanner Linsley explains the philosophy and mechanics behind createServerFn. He directly addresses how server-only code is removed from the client bundle.
About 22 minutes in, watch the createServerFn explanation. Pay close attention to his explanation around 24:30 where he states: '...when we bundle your application, we take all of this code and we remove it from the client bundle... and instead we use an RPC client replacement...'
As Tanner explained, the process, managed by the Tanstack Start Vite plugin, can be broken down into a few key steps:
- Detection: The build tool scans your code for all instances of
createServerFn. - Extraction & Hashing: For each server function found, the build tool:
- Extracts the entire handler logic (the code that's supposed to run on the server).
- Generates a unique, stable ID for this function, typically a SHA256 hash. This is the "unique hash" you might see in network requests.
- Keeps the full function logic on the server side, mapped to its new hash ID.
- Replacement: In the client-side JavaScript bundle, the entire server function body is replaced with a small "stub" or "proxy" function.
- RPC Call: This client-side stub has the same name as your original function, but its only job is to make a
fetchrequest (an RPC call) to a special API endpoint, passing the function's unique hash and any arguments.
The official documentation confirms this mechanism.
Server Functions | TanStack Start React Docs
The Tanstack Start documentation provides the technical details on how these function IDs are generated and used.
First, read the section 'Function ID generation for production build'. This explains the hashing mechanism. Then, read the final 'Note' at the bottom of the page, which succinctly summarizes the entire compilation process.
This entire process ensures that your database credentials, secret API keys, and proprietary business logic defined within a createServerFn handler are never, ever present in the JavaScript files downloaded by a user's browser.
2. An Industry-Wide Pattern: The React Server Components Analogy
This concept of a build tool stripping server-only code is not unique to Tanstack Start. It's a foundational pattern for modern, full-stack JavaScript frameworks, most famously used by React Server Components (RSCs).
In the RSC model, components are server-by-default. To create a traditional interactive component, you must opt-in by adding a "use client" directive at the top of the file. The bundler uses this directive to create a "client bundle" that includes the client components, while the server components' code is kept on the server.
createServerFn can be seen as the inverse of this: everything is client-by-default, and you use createServerFn to opt-in to server-only execution for specific functions.
Understanding React Server Components
This article on understanding React Server Components provides excellent context on the role of the bundler in separating server and client code.
Read the sections 'client imports' ('Client Components Imported Into Server Components'), 'server imports' ('Server Components Imported Into Client Components'), and 'bundler responsibilities' ('Bundlers: The Unsung Heroes'). Notice the emphasis on the bundler's responsibility to prevent server code from being sent to the client. This directly parallels how Tanstack Start handles server functions.
Understanding this parallel is valuable. It shows that Tanstack Start is built on robust, industry-vetted architectural patterns for creating secure and efficient full-stack applications.
3. Practical Verification: Inspecting the Production Bundle
Now, let's get our hands dirty and prove it. We will create a server function with a "canary"—a unique secret string—and then hunt for it in the production build output.
Step 1: Create the Server Function with a Canary
In your Tanstack Start project, create a new file, for example, at src/serverFns/canary.ts.
// src/serverFns/canary.ts
import { createServerFn } from '@tanstack/react-start';
export const getSecretCanary = createServerFn('GET', async () => {
// This is our secret string. If the build process works correctly,
// this string should NEVER appear in the client-side JavaScript files.
const secretMessage = 'SECRET_CANARY_STRING_f9b3e2a1-c8d4-4e6f-8a7b-9c1d0e2f3a4b';
console.log(`Server function is executing. Secret is: ${secretMessage}`);
return { canary: 'chirp' };
});
Step 2: Call the Function from a Client Component
Now, let's call this function from a route component, for instance, src/routes/index.tsx.
// src/routes/index.tsx
import { createFileRoute } from '@tanstack/react-router';
import { getSecretCanary } from '~/serverFns/canary';
export const Route = createFileRoute('/')({
component: HomeComponent,
});
function HomeComponent() {
return (
<div>
<h3>Welcome Home!</h3>
<button
onClick={async () => {
const result = await getSecretCanary();
alert(`Server function returned: ${result.canary}`);
}}
>
Call Canary Function
</button>
</div>
);
}
Step 3: Create a Production Build
Open your terminal and run the build command for your project.
npm run build
# or
bun run build
This will generate a dist directory containing the production-ready server and client assets.
Step 4: The Investigation
-
Navigate to the Client Assets: Open the
dist/client/assetsdirectory in your file explorer or IDE. You will see several minified JavaScript files (e.g.,index-a1b2c3d4.js). These are the files that will be downloaded by your users' browsers. -
Search for the Secret: Perform a global search across all files in the
dist/clientdirectory for our canary:SECRET_CANARY_STRING_f9b3e2a1-c8d4-4e6f-8a7b-9c1d0e2f3a4b.You will find no results. This is the definitive proof that the logic inside your
createServerFnhandler has been stripped from the client bundle. -
Find the RPC Stub: Now, search for the name of our exported function,
getSecretCanary. You will find it, but it will look very different. In the minified code, you'll see it being defined as a function that ultimately triggers a network request. It is the RPC stub. You may also see the generated hash ID associated with it.
This hands-on check provides concrete evidence of the security boundary Tanstack Start creates for you.
4. A Note on Performance: Bundle Size vs. Bandwidth
We've confirmed that server functions reduce your client-side JavaScript bundle size because the function's code isn't included. However, it's crucial to distinguish this from bandwidth usage.
Understanding React Server Components
Let's revisit the article on RSCs, which has an excellent section explaining a common point of confusion that applies equally to server functions.
Locate the section 'The Bundle Size Confusion' and read the bandwidth explanation. The key takeaway is that while the server component's (or server function's) code is not in the bundle, the data it produces (the Payload) is sent over the network, consuming bandwidth.
The core misunderstanding is that bundle size and bandwidth usage are not the same thing.
- Bundle Size: Reduced, because the server-side logic (e.g., a large library used only for data processing on the server) is not sent to the client. This improves initial page load time.
- Bandwidth Usage: The data returned by the server function is serialized and sent to the client. If your function returns a massive JSON object, you are still incurring a significant bandwidth cost for that specific network request.
As an experienced developer, this distinction is vital for making informed architectural decisions. Server functions are perfect for offloading computation and securing secrets, but they are not a magic bullet for eliminating all network traffic.
Conclusion
Today, we demystified one of Tanstack Start's most powerful features. You've seen the theory, explored the parallels in the wider ecosystem, and performed a practical analysis to confirm the behavior for yourself.
Key Takeaways:
- The Tanstack Start Vite plugin automatically extracts
createServerFnlogic during the build process. - The server-side code is replaced with a lightweight RPC stub in the client bundle, which makes a
fetchcall using a unique hash ID. - This process is verifiable by building the application and searching for sensitive strings in the
dist/clientdirectory. - This code-stripping pattern is a cornerstone of modern full-stack frameworks, analogous to how React Server Components are handled.
- It's important to differentiate between the reduction in bundle size (a win for initial load) and the bandwidth cost of the data payload returned by the function.
In our next lesson, we will build upon this server-side foundation. Now that we understand how these server-side requests are made, we will learn how to intercept and act on them by implementing server-side middleware for handling requests and responses. This will allow us to add cross-cutting concerns like logging, authentication checks, and custom headers to our server functions.