08 Dec 2024 - 7 min read
Next.js is a powerful React framework that enables both server-side rendering (SSR) and static site generation (SSG). Its standout features include the use server and use client directives, which help developers manage where code should be executed—either on the server or client side.
An important aspect to understand is that the use client directive acts as a door to send data and JavaScript from the server to the client, while use server acts as a backdoor to send JSON to the server. This distinction is crucial for optimizing performance, enhancing security, and maintaining a clean codebase
use client Directive
Misconception: The use client directive means the component will only run on the client side and will not be rendered or considered by the server.
Reality: The term use client naturally suggests exclusive client-side execution, leading developers to believe that such components are entirely detached from server rendering. In reality, Next.js aims to pre-render as much HTML as possible, including client components, to enhance SEO and performance. After the initial HTML is rendered, React takes over on the client side, hydrating the UI.
Key Insight: The use client directive acts as a door from the server to the client, allowing the server to send data and JavaScript to the client. This enables interactive components to function correctly once the initial server-rendered HTML is loaded.
Common Errors and How to Avoid Them
Hydration Mismatches: Occur when the HTML generated on the server doesn't match the HTML generated on the client, causing errors during React's hydration process. This typically happens due to dynamic values like Date.now(), Math.random(), or locale-specific data. To avoid this, precompute dynamic values on the server and pass them as props.
Why Date.now() Causes Issues:
Date.now() returns the number of milliseconds elapsed since January 1, 1970 00:00:00 UTC. Because it fetches the current timestamp, its value changes every millisecond. When Date.now() is called on the server to render initial HTML, it produces a specific timestamp. However, when the same code runs on the client during hydration, it generates a different timestamp due to the time elapsed between the server render and client render.
Accessing document or window: Directly accessing document or window in a server-side environment will cause errors. Wrap such code in a useEffect hook to ensure it only runs on the client side, or use conditionals to check if the code is running in a browser environment.
useEffect(() => { if (typeof document !== 'undefined') { // Safe to use `document` here } }, []);
Data Fetching Conflicts: Arise when both server and client run data fetching logic, causing redundant requests or mismatches in the fetched data. To avoid this, use the use server directive to ensure data fetching logic is executed only on the server.
use server Directive
Misconception: Many developers think that using use server denotes a server component and use it unnecessarily.
Reality: The use server directive is not for marking components as server components but for specifying that the enclosed functions should be executed on the server. This is crucial for tasks that involve sensitive operations or require server-side resources, like database connections.
Key Insight: The use server directive acts as a backdoor to send JSON and other data securely to the server, ensuring that sensitive operations are handled appropriately on the server side.
Endpoint Exposure
Crucial Point: Any function marked with use server and imported into a route's import tree becomes an exposed endpoint, even if it's not directly called in the client-side code. This can introduce potential security risks if sensitive operations are exposed unintentionally. Additionally, every asynchronous function defined within a file will be exposed as an endpoint if the use server directive is placed at the top level of the file.
Example:
// serverFunctions.ts export const fetchData = async () => { 'use server'; // Server-side operation const data = await getSensitiveDataFromDatabase(); return data; };
// clientComponent.tsx import { fetchData } from './serverFunctions'; const ClientComponent = () => { const handleClick = async () => { const data = await fetchData(); console.log(data); }; return <button onClick={handleClick}>Fetch Data</button>; };
In this example:
Security Implications
Because use server can expose functions as endpoints, you must be cautious:
Performance Considerations
Best Practices
Use Wisely:
Security Measures:
The use server
and use client
directives in Next.js are powerful tools that help manage where code is executed, enhancing both security and performance. By understanding their roles and implications, you can use these directives to build more secure, efficient, and robust applications. Remember to use use server
judiciously and be mindful of endpoint exposure to maintain a secure codebase.
You can check out live examples of these concepts and use cases in the blog post application that I am working on with SSR. Feel free to check it out and contribute if you want to. I am striving to form a structured way to develop with these features of Next.js that is scalable and effective. If you have any questions or need further clarification, feel free to reach out through the contact page.