General Guide: See Global LCP Guide for universal concepts and fixes.
What is LCP?
Largest Contentful Paint measures when the largest content element becomes visible. Google recommends LCP under 2.5 seconds. TinaCMS is a Git-backed headless CMS that integrates with Next.js, Astro, and other SSG/SSR frameworks. Content is stored as Markdown/MDX in your Git repository, and images are stored in the repo's public/ directory or a connected media store (Cloudinary, S3). LCP depends on your framework's rendering strategy (SSG vs. SSR), image optimization pipeline, and TinaCMS client bundle overhead.
TinaCMS-Specific LCP Causes
- TinaCMS client bundle in production -- the TinaCMS editing overlay JavaScript (~150KB+) loads on every page if not properly gated to admin/edit mode
- Git-backed images without optimization -- images stored in
public/uploads/serve at original resolution with no CDN transforms - SSR instead of SSG -- pages using
getServerSidePropsinstead ofgetStaticPropsre-render on every request - Large MDX component bundles -- custom MDX components (interactive charts, embeds) add JavaScript that blocks rendering
- No image CDN -- without Cloudinary or similar integration, images bypass any optimization pipeline
Fixes
1. Gate TinaCMS Client to Edit Mode
// In your Next.js _app.tsx or layout
import dynamic from 'next/dynamic';
// Only load TinaCMS in edit mode
const TinaProvider = dynamic(
() => import('../.tina/components/TinaProvider'),
{ ssr: false }
);
export default function App({ Component, pageProps }) {
const isEditMode = pageProps.__tina_editMode;
if (isEditMode) {
return (
<TinaProvider>
<Component {...pageProps} />
</TinaProvider>
);
}
return <Component {...pageProps} />;
}
2. Use Static Generation
// In your page component ([slug].tsx)
import { client } from '../.tina/__generated__/client';
// Use getStaticProps + getStaticPaths for SSG
export async function getStaticProps({ params }) {
const { data } = await client.queries.post({
relativePath: `${params.slug}.mdx`,
});
return { props: { data }, revalidate: 60 }; // ISR: revalidate every 60s
}
export async function getStaticPaths() {
const { data } = await client.queries.postConnection();
return {
paths: data.postConnection.edges.map(edge => ({
params: { slug: edge.node._sys.filename },
})),
fallback: 'blocking',
};
}
3. Optimize Images
With Cloudinary media store:
// In your TinaCMS media config (tina/config.ts)
import { TinaCloudMediaStore } from 'tinacms-cloudinary';
export default defineConfig({
media: {
tina: {
// Use Cloudinary for automatic optimization
mediaRoot: 'uploads',
publicFolder: 'public',
},
},
});
// In your component -- use Cloudinary transforms
const optimizedUrl = imageUrl.replace(
'/upload/',
'/upload/w_1200,q_80,f_auto/'
);
With Next.js Image component:
// In your MDX components or page templates
import Image from 'next/image';
export function HeroImage({ src, alt }) {
return (
<Image
src={src}
alt={alt}
width={1200}
height={630}
priority // Preloads the LCP image
quality={80}
style={{ width: '100%', height: 'auto', objectFit: 'cover' }}
/>
);
}
Without a framework image component:
# Optimize images in your repo before committing
find public/uploads -name "*.jpg" -exec mogrify -strip -quality 80 -resize "1920>" {} \;
find public/uploads -name "*.png" -exec optipng -o2 {} \;
4. Preload Critical Assets
// In your Next.js _document.tsx or layout <head>
import Head from 'next/head';
export function PageHead({ heroImage }) {
return (
<Head>
{heroImage && (
<link rel="preload" as="image" href={heroImage} />
)}
<link rel="preconnect" href="https://assets.tina.io" />
</Head>
);
}
5. Lazy-Load Heavy MDX Components
// In your MDX component mapping
import dynamic from 'next/dynamic';
const InteractiveChart = dynamic(
() => import('../components/InteractiveChart'),
{ ssr: false, loading: () => <div style={{ minHeight: 400 }} /> }
);
export const mdxComponents = {
InteractiveChart,
// ... other components
};
Measuring LCP on TinaCMS Sites
- Next.js Analytics (if using Next.js) -- enable
experimental.webVitalsAttributionfor automatic Core Web Vitals reporting - PageSpeed Insights -- test both the homepage and content-heavy MDX pages
- Check bundle size --
npx next-bundle-analyzerto verify TinaCMS client is not in production bundles - Compare SSG vs. SSR -- pages using
getStaticPropsshould have significantly lower TTFB thangetServerSideProps
Analytics Script Impact
- TinaCMS itself has no analytics impact in production (it is a build-time/edit-time tool)
- Place analytics scripts after framework hydration completes
- Use Next.js
Scriptcomponent withstrategy="lazyOnload"for non-critical tracking - If using Astro, place scripts with
is:inlineordeferin your layout