All pages are **server components** by default. Only pages with `'use-client'` on top are **client components**. **Server components fetch data on the server and pre-render the HTML.** For example, in the following page: ```tsx import LikeButton from '@/app/ui/like-button' import { getPost } from '@/lib/data' export default async function Page({ params }: { params: { id: string } }) { const post = await getPost(params.id) return (

{post.title}

{/* ... */}
) } ``` ```tsx 'use client' import { useState } from 'react' export default function LikeButton({ likes }: { likes: number }) { // ... } ``` The `getPost` function is run server-side to get the data and render the whole `div` tag except for the `LikeButton` which is rendered client-side. Using any hooks e.g. `useQuery` or `useMutation` requires us to do so inside a `client component`. We can also have `Next.js` fetches data and pre-renders HTML **at build time and on periodical revalidations** using [**Incremental Static Regeneration (ISR)**](https://nextjs.org/docs/app/guides/incremental-static-regeneration) as follows: ```tsx interface Post { id: string title: string content: string } // Next.js will invalidate the cache when a // request comes in, at most once every 60 seconds. export const revalidate = 60 // For each `post` in the array returned by `generateStaticParams`, // we will pre-render and cache a corresponding page at build time and on later revalidations. // If a request comes in for a path that hasn't been generated, // i.e. if there is a request for a post not retrieved when `generateStaticParams` was last run, // Next.js will server-render the page on-demand. export const dynamicParams = true // or false, to 404 on unknown paths export async function generateStaticParams() { const posts: Post[] = await fetch('https://api.vercel.app/blog').then((res) => res.json() ) return posts.map((post) => ({ id: String(post.id), })) } export default async function Page({ params, }: { params: Promise<{ id: string }> }) { const { id } = await params const post: Post = await fetch(`https://api.vercel.app/blog/${id}`).then( (res) => res.json() ) return (

{post.title}

{post.content}

) } ``` SSR with `@apollo/client`: ```tsx import { Wrapper } from '@/components' import { SESSION_COOKIE_NAME } from '@/constants' import { MeDocument, PostDocument } from '@/generated/graphql/graphql' import { createApolloClient } from '@/lib' import { Heading, Text } from '@chakra-ui/react' import { cookies } from 'next/headers' import { ClientSection } from './ClientSection' interface Props { params: { id: string } } const PostPage: React.FC = async ({ params: { id } }) => { const cookieStore = cookies() const cookie = cookieStore.get(SESSION_COOKIE_NAME)?.value const apollo = await createApolloClient(cookie) const { data } = await apollo.query({ query: PostDocument, variables: { id } }) const { data: meData } = await apollo.query({ query: MeDocument }) return ( { data?.post ? <> {data.post.title} Posted by {data.post.author.username} {data.post.content} { data?.post?.authorID == meData?.me?.id && } : <>Post not found! } ) } export default PostPage ``` where `createApolloClient` is: ```tsx 'use server' import { SESSION_COOKIE_NAME } from '@/constants' import { from, HttpLink, NormalizedCacheObject } from '@apollo/client' import { onError } from '@apollo/client/link/error' import { ApolloClient, InMemoryCache, registerApolloClient } from '@apollo/experimental-nextjs-app-support' const errorLink = onError(({ graphQLErrors, networkError }) => { graphQLErrors?.forEach(({ message, locations, path }) => { console.log( `[GraphQL error]: Message: ${message}, Location: ${locations}, Path: ${path}` ) }) if (networkError) console.error(`[Network error]: ${networkError}`) }) // This is the server-side client export const createApolloClient = async (cookie?: string) => registerApolloClient>(() => { const httpLink = new HttpLink({ uri: process.env.NEXT_PUBLIC_BACKEND_URI, credentials: 'include', headers: { cookie: cookie ? `${SESSION_COOKIE_NAME}=${cookie}` : '' } }) return new ApolloClient({ ssrMode: true, link: from([errorLink, httpLink]), cache: new InMemoryCache() }) }) ``` If we use `react-query`, we can combine the initial server-side data fetching and subsequent `useQuery` calls as follows: ```tsx // app/posts/page.tsx import { dehydrate, HydrationBoundary, QueryClient, } from '@tanstack/react-query' import Posts from './posts' export default async function PostsPage() { const queryClient = new QueryClient() await queryClient.prefetchQuery({ queryKey: ['posts'], queryFn: getPosts, }) return ( // Hydration is when React converts the pre-rendered HTML from the server into a fully interactive application by attaching event handlers. // Dehydration, on the other hand, is when an interactive JavaScript object is converted into HTML. // Here we are converting the queryClient which contains data fetched server-side into HTML and passing that HTML into HydrationBoundary, which is a Client component. // When HydrationBoundary is rendered client-side, the HTML will be hydrated, i.e. the data fetched server-side will be integrated with useQuery seamlessly. ) } ``` ```tsx // app/posts/posts.tsx 'use client' export default function Posts() { // This useQuery could just as well happen in some deeper // child to , data will be available immediately either way const { data } = useQuery({ queryKey: ['posts'], queryFn: () => getPosts(), }) // This query was not prefetched on the server and will not start // fetching until on the client, both patterns are fine to mix. const { data: commentsData } = useQuery({ queryKey: ['posts-comments'], queryFn: getComments, }) // ... } ```