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 { getOrCreateQueryClient } from "better-blog/queries";
import { createBlogServerAdapter } from "better-blog/server/pages";
import { Metadata } from "next";
import { blogProvider } from "@/lib/blog-data-provider";
// Create query client for React Query
const queryClient = getOrCreateQueryClient();
// Create the server adapter
const serverAdapter = createBlogServerAdapter({
provider: blogProvider,
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.generateNextMetadata(all?.join("/")) as unknown as Metadata;
};
// Main page component
export default async function BlogPage({
params,
}: {
params: Promise<{ all: string[] | undefined }>;
}) {
const { all } = await params;
const { BlogServerRouter } = serverAdapter;
return <BlogServerRouter 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 { type BlogUIComponents, BlogProvider } from "better-blog/context";
import { getOrCreateQueryClient } from "better-blog/queries";
// Next.js components to be used in the blog
const components: BlogUIComponents = {
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 = getOrCreateQueryClient();
const router = useRouter();
return (
<QueryClientProvider client={queryClient}>
<BlogProvider
localization={{
BLOG_LIST_TITLE: "Blog Posts",
}}
components={components}
basePath="/posts"
adminPermissions={{
// 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}
</BlogProvider>
<ReactQueryDevtools initialIsOpen={false} />
</QueryClientProvider>
);
}