I’m a bit confused how to make Nextjs paradigm work with pre-rendering something in a server component via Apollo. I get an error when I try to employ this approach:
'use client';
import {useSelectedLayoutSegments} from 'next/navigation';
import {useLocale} from 'next-intl';
import dynamic from 'next/dynamic';
const BreadcrumbsRSC = dynamic(() => import('./Breadcrumbs.server'), {ssr: true});
export default function Breadcrumbs() {
const segments = useSelectedLayoutSegments();
const locale = useLocale();
return <BreadcrumbsRSC segments={segments ?? []} locale={locale as any} />;
}
This is supposed to load/use a breadcrumb that is generated on the server (no need to read code, point is, this server component does some API shenanigans and renders a breadcrumb):
import { breadcrumbLabels } from '@/i18n/pathnames';
import { getClient } from '@/lib/client';
import { gql } from '@apollo/client';
import { UrlLocale, supportedLocales } from '@/i18n/settings';
import { categorySlugs } from '@/i18n/pathnames';
import Link from 'next/link';
import { headers } from 'next/headers'
import {getLocale} from 'next-intl/server';
// GraphQL query used when resolving business slug
const GET_BUSINESS_NAME = gql`
query GetBusinessName($slug: String!, $language: LanguageCodeEnum) {
business(slug: $slug, language: $language) {
name
}
}
`;
// Helper: capitalise words for fallback labels
declare function capitalizeWords(str: string): string;
function capitalizeWords(str: string): string {
return str
.replace(/-/g, ' ')
.replace(/\b\w/g, (c) => c.toUpperCase());
}
// Resolve a dynamic segment label. We try, in order:
// 1. Category slug mapping
// 2. Business GraphQL lookup
// 3. Fallback humanised slug
async function resolveDynamicLabel(
locale: UrlLocale,
slug: string
): Promise<string> {
// 1. Category slug?
const matchedKey = Object.keys(categorySlugs).find(
(key) => categorySlugs[key as keyof typeof categorySlugs][locale] === slug
);
if (matchedKey) {
return capitalizeWords(matchedKey);
}
// 2. Business?
// Convert url locale (en-us) to canonical (en_us) used by backend
const canonical =
supportedLocales.find((l) => l.url === locale)?.canonical || 'en_us';
try {
const { data } = await getClient().query({
query: GET_BUSINESS_NAME,
variables: { slug, language: canonical },
context: { fetchOptions: { next: { revalidate: 3600 } } },
});
if (data?.business?.name) return data.business.name as string;
} catch {
/* ignore – treat as unknown slug */
}
// 3. Fallback
return capitalizeWords(slug);
}
export default async function BreadcrumbsServer({
segments = [],
locale,
}: {
segments?: string[] | null;
locale: UrlLocale;
}) {
// const headersList = await headers()
// console.log(headers);
// const pathname = headersList.get('x-pathname') ||
// headersList.get('x-invoke-path') ||
// new URL(headersList.get('referer') || '').pathname
// const safeSegments = pathname.split('/').filter(Boolean)
// locale = await getLocale();
const safeSegments = segments ?? [];
// Build incremental hrefs so that we can compare against pathname patterns
const parts: { href: string; slug: string }[] = [];
let href = '';
for (const seg of safeSegments) {
href += `/${seg}`;
parts.push({ href, slug: seg });
}
// Resolve labels in parallel
const labels: string[] = await Promise.all(
parts.map(async ({ href: partHref, slug }) => {
// 1. Static label lookup by full path (root already stripped off locale)
const staticLabelEntry = breadcrumbLabels[partHref as keyof typeof breadcrumbLabels];
if (staticLabelEntry && staticLabelEntry[locale]) {
return staticLabelEntry[locale];
}
// 2. Dynamic resolution chain
return await resolveDynamicLabel(locale, slug);
})
);
if (parts.length === 0) return null;
return (
<nav className="mb-6 text-sm">
{parts.map((p, i) => (
<span key={p.href}>
{i < parts.length - 1 ? (
<Link href={`/${locale}${p.href}`}>{labels[i]}</Link>
) : (
<span aria-current="page">{labels[i]}</span>
)}
{i < parts.length - 1 && ' / '}
</span>
))}
</nav>
);
}
This approach throws an error:
The export registerApolloClient was not found in module [project]/node_modules/@apollo/client-integration-nextjs/dist/index.browser.js [app-client] (ecmascript) <exports>.
Did you mean to import ApolloClient?
All exports of the module are statically known (It doesn't have dynamic exports). So it's known statically that the requested export doesn't exist.
Which seems to be when you try to use getclient/server apollo approach on a client component… which, I guess makes sense, I’m importing it in a client component…
The reason why I wanted to do that, is to use it on a page that is a client component.
So, I’m a bit confused how do you actually use a component such as my breadcrumb on any page/component, whether it’s server-side or client-side? I need to/want to dynamically construct the breadcrumb on the server (since it has several dynamic data with titles/slugs).
Is the only way to do this to embed the breadcrumb on a high-level server-side page, and then pass it to the client component in which I want to render it?
Such as this?
export default async function DirectoryPage({ params }: DirectoryPageProps) {
const { locale } = await params;
return (
<div>
<Breadcrumbs
/>
<DirectoryPageComponent
breadcrumbs={<Breadcrumbs />}
locale={locale} />
</div>
)
}
This works but it’s a bit verbose.
I’m sorry, I know this is maybe more a Next.js than an Apollo question… But I wasn’t sure if this is expected behavior with the apollo error. Is the pattern that I’d prefer impossible? I read through the docs on the apollo-next-integration GitHub but I wasn’t really sure, especially since I’m still brand-new to server-side components.
Thanks for the suggestions!