Next.js App Router
SSR/SSG with server prefetch and client hydration
This renders all blog routes under /posts/...
with server prefetch + client hydration. Provide separate data providers for server and client.
1) app/posts/[[...all]]/page.tsx
(server)
Create the Better Blog server adapter, export Next.js data functions, and render with server prefetch + client hydration.
import { BlogDataProvider, getDefaultQueryClient } from "better-blog";
import { createServerAdapter } from "better-blog/server";
import { Metadata } from "next";
import { blogConfig } from "@/lib/blog-data-provider";
// Implement your data provider here
const serverBlogConfig: BlogDataProvider = {
...
};
// Create query client for React Query
const queryClient = getDefaultQueryClient();
// Create the server adapter
const serverAdapter = createServerAdapter(
blogConfig,
queryClient
);
// Export the required Next.js functions
export const generateStaticParams = serverAdapter.generateStaticParams;
export const generateMetadata: (context: {
params: Promise<{ all: string[] | undefined }>;
}) => Promise<Metadata> = async ({ params }) => {
const { all } = await params;
return serverAdapter.generateMetadata(all?.join("/"));
};
// Main page component
export default async function BlogPage({
params,
}: {
params: Promise<{ all: string[] | undefined }>;
}) {
const { all } = await params;
const { Entry } = serverAdapter;
return <Entry path={all?.join("/")} queryClient={queryClient} />;
}
2) app/posts/[[...all]]/layout.tsx
(server)
Wrap the route with your Provider
to supply Query Client, Better Blog context, and router bindings.
import { ReactNode } from 'react';
import { Provider } from '@/components/providers';
export default function BlogLayout({
children
}: {
children: ReactNode
}) {
return (
<Provider>
{children}
</Provider>
);
}
3) components/providers.tsx
(client)
Client-side provider wiring Next.js Link
/Image
, React Query, navigation helpers, and optional admin UI.
"use client";
import { ReactNode } from "react";
import Link from "next/link";
import Image from "next/image";
import { useRouter } from "next/navigation"
import { QueryClientProvider } from "@tanstack/react-query";
import { ReactQueryDevtools } from '@tanstack/react-query-devtools'
import { BetterBlogContextProvider } from "better-blog/client";
import { ComponentsContextValue, getDefaultQueryClient } from "better-blog";
// Next.js components to be used in the blog
const components: ComponentsContextValue = {
Link: ({ href, children, className }) => (
<Link href={href} className={className}>
{children}
</Link>
),
Image: ({ src, alt, className, width, height }) => (
<Image
src={src}
alt={alt}
className={className}
width={width || 800}
height={height || 400}
/>
),
};
export function Provider({ children }: { children: ReactNode }) {
const queryClient = getDefaultQueryClient();
const router = useRouter();
return (
<QueryClientProvider client={queryClient}>
<BetterBlogContextProvider
localization={{
BLOG_LIST_TITLE: "Blog Posts",
}}
components={components}
basePath="/posts"
adminUiOptions={{
// implement your own admin ui logic here
canCreate:true,
canUpdate:true,
canDelete:true
}}
navigate={router.push}
replace={router.replace}
uploadImage={async (file) => {
console.log("uploadImage", file);
// implement your own image upload logic here
return "https://placehold.co/400/png";
}}
>
{children}
</BetterBlogContextProvider>
<ReactQueryDevtools initialIsOpen={false} />
</QueryClientProvider>
);
}