10 things about component composition in nextjs
Tags: nextjs, react, webdev, javascript
Published: 2024-04-05 10:20:21

In this blog, I will talk about 10 things you should know about component composition in Next.js. You will learn:


1. Different composition patterns with Server and client components
2. Pattens that don't work
3. How to use third-party packages that use browser api
4. When to use server and client components
5. How to share data between components
6. Tips on performance

## What is component composition in Nextjs?

In simple words, it is just a way to combine server and client components in Next.js.

## React tree and obvious pattern

Few things to know:

1. React uses a tree structure to render components like the actual DOM. See the image below.
2. All the components in nextjs are now server components.
3. You can make a client component by adding `use client` directive at the top of the file.
4. It is obvious that you can nest server components inside server components and client components inside client components.
   ![React server and client component tree ](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/pgcyi7n5etprb9afv7sk.png)

Let's learn the 10 things you should know about component composition in Next.js.

## 1. Client component inside server component

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/m186ij7kwhcnbipk54c5.png)

You can use client components inside server components without any problem. Check the code below.

```javascript
'use client'
// client component
import { useState } from 'react' // client side module

export default function ClientComponent() {
	const [count, setCount] = useState(0)
	return (
		<div>
			<button onClick={() => setCount(count + 1)}>Increment</button>
			<h1>Client Component</h1>
			<p>Count: {count}</p>
		</div>
	)
}
```

```javascript
// server component
import fs from 'fs' // server side module
import ClientComponent from './clientComponent'

export default function ServerComponent() {
	return (
		<div>
			<h1>Server Component</h1>
			<ClientComponent />
		</div>
	)
}
```


I have video explaining all these points. Check it out.

{% youtube Gtc5okZWeHA %}


## 2. All components are client components

You might have a lot of client components and adding `use client` directive to all of them is a bit of a hassle. But if all the children components of a client component that has `use client` directive, will be treated as a client component. Check the code and image below.
![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/mr4az2t629avzz7wflaa.png)

```javascript
'use client'

// client component
import ClientComponentChild from './clientComponentChild'

export default function ClientComponent() {
	return (
		<div>
			<h1>Client Component</h1>
			<ClientComponentChild />
		</div>
	)
}
```

```javascript
// client component child
// No need to add `use client` directive
import { useState } from 'react'

export default function ClientComponentChild() {
	return (
		<div>
			<h1>Client Component Child</h1>
		</div>
	)
}
```

## 3. No server component inside the client component

You simply can't use server components inside client components. It will throw an error. Check the code below.

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/5ucuoy3zitv0ox2rhjce.png)

```javascript
// client component
import ServerComponent from './serverComponent' // Error

export default function ClientComponent() {
	return (
		<div>
			<h1>Client Component</h1>
			<ServerComponent />
		</div>
	)
}
```

```javascript
// server component
import fs from 'fs' // server side module

export default function ServerComponent() {
	return (
		<div>
			<h1>Server Component</h1>
		</div>
	)
}
```

You will see the error below.

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/0cf6xbj1co8ync7xb8v0.png)

It is trying to import the `fs` module on the client side but it is not available on the client side.

## 4. Pass server component as a prop to the client component

Even though you can't use server components inside client components, you can pass server components as a prop to client components.

This is useful when you want to have some kind of Provider like React Context or Redux Provider. You want to wrap the app by the provider. You can do that in the main layout file. But context or any kind of state is not available on server components.

So, You have two options:

1. Make the main layout file a client component.
2. Or you can pass the server component as a prop to the client component and wrap the app with the provider. Check the code below.

```javascript
'use client' // client component
// Provider.jsx

import React, { createContext, useState } from 'react'

// Create a context
const AppContext = createContext()

// Create a provider component
const AppProvider = ({ children }) => {
	// Define state or any other data you want to share
	const [user, setUser] = useState(null)

	// Define any functions or methods you want to expose
	const loginUser = userData => {
		setUser(userData)
	}

	const logoutUser = () => {
		setUser(null)
	}

	// Define the context value
	const contextValue = {
		user,
		loginUser,
		logoutUser,
	}

	// Return the provider with its value and wrapped children
	return (
		<AppContext.Provider value={contextValue}>{children}</AppContext.Provider>
	)
}

export default AppProvider
```

```javascript
// layout.jsx
import AppProvider from './Provider'

export default function RootLayout({ children }) {
	return (
		<html lang='en'>
			<body className={inter.className}>
				{/* Client component as parent of server component */}
				<AppProvider>{children}</AppProvider>
			</body>
		</html>
	)
}
```

Even though `AppProvider` is a client component, and it is wrapping the entire app, all of your components will be still server components by default. However you can't consume the context in server components. Because the server component doesn't support any state.

## 5. keep the server-only code to server components

Sometimes, you write code for server only. Like fetching data from the database, reading files, etc. You should keep those codes in server components. But accidentally you might use those codes in client components. For example:

```javascript
export async function getData() {
	const url = 'https://jsonplaceholder.typicode.com/todos/1'

	const res = await fetch(url, {
		headers: {
			authorization: process.env.API_KEY,
		},
	})

	return res.json()
}
```

This `getData` function can be used both on the server and client side. But it should be used only on the server side because you are accessing a secret environment variable. To prevent this, you can use a package called `server-only`. You just need to import the package at the top of the file.

```javascript
import 'server-only'

export async function getData() {
	const url = 'https://jsonplaceholder.typicode.com/todos/1'

	const res = await fetch(url, {
		headers: {
			authorization: process.env.API_KEY,
		},
	})

	return res.json()
}
```

## 6. Pass server data to the client component

Sometimes you need to pass some data from the server to client component. For instance, some data that you have fetched from the database and you want the client component to have that data. You can pass the data as props. However, the data should be [Serializable](https://react.dev/reference/react/use-server#serializable-parameters-and-return-values).

You can pass string, number, boolean, array, object, etc. Check the documentation for full list.
You can't pass functions(that are not server action), jsx etc.

```javascript
// server component

export default async function ServerComponent() {
	const data = await getData() // getting some data but could be anything

	return (
		<div>
			<h1>Server Component</h1>
			<ClientComponent
				data={data} // Works
				callback={() => console.log('hello')} // Won't work
			/>
			/>
		</div>
	)
}
```

## 7. Share data between server components using a single request

Suppose you are fetching some data on one server component and you want to use that data in another server component. You don't need to send multiple requests. If you use the `fetch` function then react will automatically cache the data. So, only one request will be sent.

```javascript
async function ServerComponent1() {
	// Fetching and caching the data
	const data = await fetch('https://api.example.com/data')
	return (
		<div>
			<h1>Server Component 1</h1>
			<ServerComponent2 data={data} />
		</div>
	)
}

async function ServerComponent2() {
	// Use the cached data
	const data = await fetch('https://api.example.com/data')
	return (
		<div>
			<h1>Server Component 2</h1>
			<p>{data}</p>
		</div>
	)
}

export default function App() {
	return (
		<div>
			<ServerComponent1 />
			<ServerComponent2 /> {/* Won't send another request */}
		</div>
	)
}
```

## 8. No browser API in server components

You can't use browser API in server components. You can't use `window`, `document`, `localStorage` etc. It will throw an error. Check the code below.

```javascript
// server component
export default function ServerComponent() {
	return (
		<div>
			<h1>Server Component</h1>
			<p>{window.location.href}</p> // Error
		</div>
	)
}
```

## 9. Third-party packages that use browser API in server components

Since server components are new a lot of third-party packages might not work with server components. Because they haven't added 'use client' directive to their code. So, you will get an error. Check the code below.

```javascript
// server component
// Some third party component that uses useState
import { ExComponent } from 'some-package'

export default function ServerComponent() {
	return (
		<div>
			<h1>Server Component</h1>
			<ExComponent /> {/* Error */}
		</div>
	)
}
```

If you use it within a client component and then use it on the server component then it will work.

```javascript
'use client'
// client component
import { ExComponent } from 'some-package'

export default function ClientComponent() {
	return (
		<div>
			<h1>Client Component</h1>
			<ExComponent /> {/* Works */}
		</div>
	)
}
```

Or you can wrap the third-party component with a client component like below.

```javascript
'use client'
import { ExComponent } from 'some-package'

export default ExComponent
```

## 10. You should move the client component down the tree

Server components are cheap and faster to send to the browser. Since they don't have javascript. And client components are the opposite.
Like I mentioned before, once you make a client component, all of its children will be treated as client components. Since they will have javascript, it will have a bigger bundle size. So, if you put the client components at the top of the tree, it will increase bundle size.

**Good:**

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/rkjp5qw9mvgvjbgm4kzm.png)

**Bad(but still depends on the situation):**

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/3ry243o42q508y6nifyl.png)

That's it. I hope you learned something new. If you have any questions, feel free to ask in the comment section below.
Please subscribe to my YouTube, it really motivates me to create more content.