Skip to main content

Server Components

Server Components are the primary way we fetch and render data in the Next.js Supabase SaaS kit.

How do they work?

  1. 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.
  2. 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.
  3. 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:

  1. The server runs this component
  2. It fetches the tasks directly from Supabase
  3. It creates the HTML with all the tasks
  4. 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>
)
}