Episode 27 integrates GraphQL with Next.js: API routes with Apollo Server, Server-Side Rendering with getServerSideProps and hydration, Static Site Generation with getStaticProps and ISR, App Router with Server Components, authentication with cookies and NextAuth, and deployment to Vercel.

Next.js is the most popular React framework for full-stack development — and combining it with GraphQL is very natural. Episode 27 integrates the two thoroughly, from the server side to rendering strategies. We'll build a GraphQL API route, implement SSR and SSG, explore the App Router with Server Components, discuss authentication with cookies, and close with deployment to Vercel.
To expose GraphQL as a Next.js API route, install npm install @apollo/server @as-integrations/next, then use the @as-integrations/next integration:
import { ApolloServer } from "@apollo/server";
import { startServerAndCreateNextHandler } from "@as-integrations/next";
const server = new ApolloServer({
typeDefs: `type Query { hello: String }`,
resolvers: {
Query: { hello: () => "Halo dari Next.js" },
},
});
const handler = startServerAndCreateNextHandler(server);
export { handler as GET, handler as POST };Save this file as app/api/graphql/route.ts, and the GraphQL endpoint becomes available at /api/graphql. Note: the handler can be exported as both GET and POST.
A Next.js API route runs as a serverless function. The consequences: server instances can "sleep" and be restarted (cold starts), and database connections must not be created at the global scope where they'd spawn too many connections. Use connection pooling (episode 37) and avoid in-memory state across requests.
import { ApolloClient, InMemoryCache, createHttpLink } from "@apollo/client";
export async function getServerSideProps({ params }) {
const client = new ApolloClient({
ssrMode: true,
link: createHttpLink({ uri: process.env.GRAPHQL_URL }),
cache: new InMemoryCache(),
});
await client.query({
query: GET_USER,
variables: { id: params.id },
});
return {
props: { apolloState: client.extract(), id: params.id },
};
}The ssrMode: true and client.extract() patterns are important: after the query completes on the server, the cache state is extracted and sent as props, then hydrated on the client. This way the browser doesn't need to re-query, and the data doesn't flicker.
After SSR, the client is built with the initialState from the server cache so the server and client renders are consistent. Apollo Client provides getDataFromTree to collect all queries during the server render — this setup is provided by the @apollo/client pattern with getDataFromTree for React.
export async function getStaticProps() {
const client = createApolloClient();
await client.query({ query: GET_POSTS });
return { props: { apolloState: client.extract() } };
}With Incremental Static Regeneration (ISR), static pages can be revalidated periodically. Pages regenerate in the background at most every 60 seconds when a new request comes in. On-demand revalidation even lets you trigger regeneration via an API or webhook — perfect after a GraphQL mutation changes content.
The Next.js App Router introduces Server Components, which can fetch data directly from GraphQL without client-side state:
export const revalidate = 60;
export default async function HomePage() {
const data = await fetch(process.env.GRAPHQL_URL, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ query: "{ posts { id title } }" }),
next: { revalidate: 60 },
}).then((r) => r.json());
return (
<main>
{data.data.posts.map((post) => (
<PostCard key={post.id} title={post.title} />
))}
</main>
);
}For mutations and interactions, components that need client state are marked "use client" and use Apollo hooks or a plain fetch library. Combining Server Components for reading data and Client Components for writing data is the dominant pattern in modern Next.js.
For full-stack authentication in Next.js:
httpOnly cookie on login, read it in middleware to protect routes.import { NextResponse } from "next/server";
export function middleware(request) {
const token = request.cookies.get("token");
if (!token) return NextResponse.redirect(new URL("/login", request.url));
return NextResponse.next();
}
export const config = { matcher: ["/dashboard/:path*"] };The refresh token pattern and NextAuth details will be combined with the GraphQL context in episode 43 for multi-tenancy.
Things to note: store all secrets (GRAPHQL_URL, JWT_SECRET, database URL) in Vercel Environment Variables, not in code. Serverless optimization: keep the bundle small, use connection pooling for the database, and move heavy tasks (image processing, cron) to separate functions.
Key takeaways:
@as-integrations/next exposes an endpoint at /api/graphql.ssrMode and extract avoids double rendering and flicker.In the next episode, episode 28, you'll learn about GraphQL with mobile development — Apollo Client in React Native, cache persistence with AsyncStorage, offline-first architecture with a mutation queue, mobile performance optimization, and Flutter integration with graphql_flutter. Your GraphQL will reach into the mobile world!