Server Components
Server Components are the primary way we fetch and render data in the Next.js Supabase SaaS kit.
How do they work?
- Server-Only Rendering: When someone visits your page, the Server Component runs on the server, fetches the data it needs, and sends the final HTML to the browser. The browser then just displays this HTML without running any of the component's React code.
- Automatic Default: In our Next.js app, every component is automatically a Server Component unless you specifically mark it with
'use client'
. This means you can freely fetch data and use server-side code without any extra setup. - Streaming: When your Server Component fetches data, it doesn't wait for everything to be ready before sending anything to the browser. Instead, it streams the content as it becomes available, making your pages feel faster.
A Simple Example
Let's say you want to show a list of tasks. Here's how it works:
// app/tasks/page.tsx
import { getTasks } from '@repo/supabase/serverHelpers'
async function TasksPage() {
// This code only runs on the server
const tasks = await getTasks()
return (
<div>
<h1>Your Tasks</h1>
{tasks.map(task => (
<div key={task.id}>
{task.title}
</div>
))}
</div>
)
}
When someone visits /tasks
:
- The server runs this component
- It fetches the tasks directly from Supabase
- It creates the HTML with all the tasks
- The browser receives and displays the HTML
The best part? All the data fetching happens on the server, making your app faster and more secure.
Server Components in JetShip provide a powerful way to render components on the server, reducing client-side JavaScript and enabling efficient data fetching. This guide explains how to effectively use Server Components in your JetShip application.
Data Fetching
Parallel Data Fetching
Use Promise.all
to fetch data in parallel:
const BlogPage = async () => {
const [posts, categories, settings] = await Promise.all([
getPosts(),
getCategories(),
getSettings()
])
return (
<div>
<BlogHeader settings={settings} />
<BlogList posts={posts} categories={categories} />
</div>
)
}
Sequential Data Fetching
When data dependencies exist:
const PostPage = async ({ params }: { params: { slug: string } }) => {
// First, get the post
const post = await getPost(params.slug)
// Then, get related posts based on categories
const relatedPosts = await getRelatedPosts(post.categories)
return (
<div>
<PostContent post={post} />
<RelatedPosts posts={relatedPosts} />
</div>
)
}
Error Handling
Implement proper error handling in Server Components:
const UserSection = async () => {
try {
const [
{ data: { user } },
{ data: usersRecord, error: userError },
{ data: rolesData, error: rolesError }
] = await Promise.all([
getAuthUser(),
getUsers(),
getAllUserRoles('*, roles (role)')
])
if (userError || rolesError) {
return (
<Alert variant="destructive">
<AlertDescription>
{userError?.message || rolesError?.message}
</AlertDescription>
</Alert>
)
}
return <Users usersRecord={usersRecord} rolesData={rolesData} />
} catch (error) {
console.error('Error in UserSection:', error)
return <ErrorComponent error={error} />
}
}
Loading States
Use Suspense for loading states:
// app/(back)/admin/users/page.tsx
import { Suspense } from 'react'
import Loader from '@repo/ui/components/Loader'
const UserPage = () => {
return (
<Suspense fallback={<Loader />}>
<UserSection />
</Suspense>
)
}
Client-Server Component Pattern
Server Component (Parent)
// ServerComponent.tsx
const ServerComponent = async () => {
const data = await fetchData()
return (
<div>
<h1>Server Rendered</h1>
<ClientComponent initialData={data} />
</div>
)
}