Hi guys!
I've been struggling with the long TTFB on my website for several days and was hoping you could help me out. The issue is that the TTFB of the initial visit to every page takes a minimum of 500ms and usually 1-2s, whereas a static website typically has a TTFB of 30-50ms. I've tested this on both Netlify and Versel, and even locally it takes more than 300ms.
Here some pieces of my code:
prismicio.ts
import * as prismic from "@prismicio/client";
import * as prismicNext from "@prismicio/next";
import config from "../slicemachine.config.json";
/**
* The project's Prismic repository name.
*/
export const repositoryName =
process.env.NEXT_PUBLIC_PRISMIC_ENVIRONMENT || config.repositoryName;
// There is no way to automate the process of chnaging an URL from /de-de to /de or /en-us to /en,
// so no need to fetch locales and use the createLocaleRedirect function in the middleware.
// That's why the locales are hardcoded.
export const LOCALES = [
{ id: "en-us", name: "English - United States", is_master: true, slug: "en" },
{ id: "de-de", name: "German - Germany", is_master: false, slug: "de" },
] as (prismic.Language & { is_master: boolean; slug: string })[];
/**
* A list of Route Resolver objects that define how a document's `url` field is resolved.
*
* {@link https://prismic.io/docs/route-resolver#route-resolver}
*/
// NOTE! needed to publish the first document of that type:
// https://community.prismic.io/t/prismic-with-nextjs-getsingle-unknown-type/12788
const routes: prismic.ClientConfig["routes"] = [
{
type: "homepage",
lang: "en-us",
path: "/",
},
{
type: "homepage",
lang: "de-de",
path: "/de",
},
// Solution pages
{
type: "solution_page",
lang: "en-us",
path: "/solutions/:uid",
},
{
type: "solution_page",
lang: "de-de",
path: "/de/solutions/:uid",
},
// Product pages
{
type: "product_page",
lang: "en-us",
path: "/products/:uid",
},
{
type: "product_page",
lang: "de-de",
path: "/de/products/:uid",
},
// Industry pages
{
type: "industry_page",
lang: "en-us",
path: "/industries/:uid",
},
{
type: "industry_page",
lang: "de-de",
path: "/de/industries/:uid",
},
// Resource library pages
{
type: "resource_library_page",
lang: "en-us",
path: "/resource-library/:uid",
},
{
type: "resource_library_page",
lang: "de-de",
path: "/de/resource-library/:uid",
},
// Resource library category pages
{
type: "resource_library_category",
lang: "en-us",
path: "/resource-library/:uid",
},
{
type: "resource_library_category",
lang: "de-de",
path: "/de/resource-library/:uid",
},
// Blog post pages
{
type: "blog_post",
lang: "en-us",
path: "/blog/:uid",
},
{
type: "blog_post",
lang: "de-de",
path: "/de/blog/:uid",
},
// Blog category pages
{
type: "blog_category",
lang: "en-us",
path: "/blog/:uid",
},
{
type: "blog_category",
lang: "de-de",
path: "/de/blog/:uid",
},
// Other pages
{
type: "page",
lang: "en-us",
path: "/:uid",
},
{
type: "page",
lang: "de-de",
path: "/de/:uid",
},
];
/**
* Creates a Prismic client for the project's repository. The client is used to
* query content from the Prismic API.
*
* @param config - Configuration for the Prismic client.
*/
export const createClient = (config: prismicNext.CreateClientConfig = {}) => {
const client = prismic.createClient(repositoryName, {
routes,
accessToken: process.env.PRISMIC_ACCESS_TOKEN,
fetchOptions:
process.env.NODE_ENV === "production"
? { next: { tags: ["prismic"] }, cache: "force-cache" }
: { next: { revalidate: 5 } },
...config,
});
prismicNext.enableAutoPreviews({
client,
previewData: config.previewData,
req: config.req,
});
return client;
};
app/[lang]/[uid]/page.tsx
import Navigation from "@/components/Navigation/Navigation";
import TopBanner from "@/components/TopBanner";
import { getLocale } from "@/lib/getLocale";
import { createClient } from "@/prismicio";
import { components } from "@/slices";
import { isFilled } from "@prismicio/client";
import { SliceZone } from "@prismicio/react";
import { Metadata } from "next";
import { notFound } from "next/navigation";
type Params = { lang: string; uid: string };
export async function generateMetadata({
params,
}: {
params: Params;
}): Promise<Metadata> {
const client = createClient();
const locale = await getLocale(params.lang);
const page = await client
.getByUID("page", params.uid, {
lang: locale,
})
.catch(() => null);
if (!page) {
return {};
}
const metadata = {} as Metadata;
if (isFilled.keyText(page.data.meta_title))
metadata.title = page.data.meta_title;
if (isFilled.keyText(page.data.meta_description))
metadata.description = page.data.meta_description;
if (isFilled.image(page.data.meta_image))
metadata.openGraph = { images: [page.data.meta_image.url] };
return metadata;
}
export default async function Page({ params }: { params: Params }) {
const client = createClient();
const locale = await getLocale(params.lang);
const globals = await client.getSingle("global_components", { lang: locale });
const page = await client
.getByUID("page", params.uid, { lang: locale })
.catch(() => notFound());
return (
<>
{isFilled.richText(globals.data.topbanner_text) && (
<TopBanner {...globals.data} />
)}
<Navigation
page={page}
currentLocale={locale}
isShadow={page.data.navigation_shadow}
theme={page.data.navigation_style}
/>
<SliceZone slices={page.data.slices} components={components} />
</>
);
}
export async function generateStaticParams({ params }: { params: Params }) {
const client = createClient();
const locale = await getLocale(params.lang);
const pages = await client.getAllByType("page", {
lang: locale,
});
return pages.map((page) => {
return {
uid: page.uid,
lang: page.lang,
};
});
}
and lib/getLocale.ts (just for case)
import { LOCALES } from "@/prismicio";
import type { Language } from "@prismicio/client";
export async function getLocale(langSlug: string): Promise<Language["id"]> {
const locale = LOCALES.find((locale) => locale.slug === langSlug);
if (locale) return locale.id;
const masterLocale = LOCALES.find((locale) => locale.is_master);
if (masterLocale) return masterLocale.id;
throw new Error("Master locale not found.");
}
As far as I understand, pages should be statically rendered during build and their content should be cached by next.js and updated on the "prismic" tag trigger, which is how the ISR works. Please correct me if I'm wrong. However, according to the Netlify logs, every request triggers SSR (I'm not sure if this is correct).
Please note, nothing were change in Prismic from yesterday.
I also tested a few examples from the prismic-community repository, and the results were surprising. The TTFB on this website - Todoop – Keep your life organized - averages between 200-500ms (please note that not much content is loaded from Prismic compared to my website) and is rarely 80-100ms, but never 30-50ms as with the static hosted.
It seems that websites with multiple languages that use Prismic have something wrong with caching and content fetches on every page load.
Any ideas?
Thanks