Install
$ agentstack add skill-mvstepanek-nextjs-ecommerce-seo-skills-seo-performance ✓ scanned · ✓ verified — works with Claude Code, Cursor, and more.
Security review
✓ PassedNo issues found. Passed automated security review. · v0.1.0 How review works →
- ✓ Prompt-injection patterns
- ✓ Secret / credential exfiltration
- ✓ Dangerous shell & filesystem operations
- ✓ Untrusted network calls
- ✓ Known-malicious package signatures
What it can access
- ✓ Network access No
- ✓ Filesystem access No
- ✓ Shell / process execution No
- ● Environment & secrets Used
- ✓ Dynamic code execution No
From automated source analysis of v0.1.0. “Used” means the capability is present in the source — more access means more to trust, not that it’s unsafe.
About
Performance & Core Web Vitals SEO Guidelines
Core Web Vitals are a Google ranking factor. Follow these rules to maintain good performance on a Next.js e-commerce site.
Targets
| Metric | Target | What it measures | |--------|--------|-----------------| | LCP (Largest Contentful Paint) | ; }
// GOOD — Only the interactive part is a client component // components/AddToCartButton.tsx "use client"; export function AddToCartButton({ productId }: Props) { const [quantity, setQuantity] = useState(1); // ... }
**Why it matters**: Client-side rendering adds to the JavaScript bundle. On category pages with 50+ products, making each product card a client component can add hundreds of KB of JS, destroying LCP.
## LCP Optimization
The LCP element on product pages is typically the main product image. On category pages, it's the first visible product image or the hero banner.
1. **Preload the LCP image** — use `priority` prop on the `` component
2. **Avoid client-side data fetching for above-fold content** — fetch product data server-side
3. **Don't lazy-load the LCP image** — `priority` disables lazy loading automatically
4. **Minimize server response time** — use ISR (Incremental Static Regeneration) for product pages
```typescript
// next.config.js — ISR for product pages
// In page.tsx:
export const revalidate = 300; // 5 minutes — fresh enough for buyers
CLS Prevention
- Always size images — use
width/heightorfillwith a sized container (see seo-images skill) - Reserve space for dynamic content — prices, stock badges, add-to-cart buttons
- Font loading — use
next/fontwithdisplay: swapandsize-adjustto prevent layout shift - Avoid injecting content above existing content — banners, promo bars, cookie consents should not push content down
// GOOD — font with swap and size-adjust
import { Inter } from 'next/font/google';
const inter = Inter({ subsets: ['latin'], display: 'swap' });
// BAD — loading fonts via causes FOUT and CLS
INP Optimization
- Minimize client-side JavaScript — less JS = faster interactions
- Use
useTransitionfor non-urgent UI updates (filter changes, sort changes on category pages) - Avoid blocking the main thread — don't run expensive computations in event handlers
- Debounce search input — don't fire a request on every keystroke
"use client";
import { useTransition } from 'react';
function CategoryFilters({ onFilterChange }: Props) {
const [isPending, startTransition] = useTransition();
function handleFilterChange(filter: string) {
startTransition(() => {
onFilterChange(filter); // Non-urgent, won't block interaction
});
}
}
Google Tag Manager (GTM)
GTM is a significant performance cost if loaded incorrectly.
// GOOD — load GTM after page is interactive
import Script from 'next/script';
// BAD — blocks page rendering
Rules:
- Always use
strategy="afterInteractive"for GTM - Never use
beforeInteractivefor analytics/tracking scripts - Store the GTM container ID in an environment variable (
NEXT_PUBLIC_GTM_ID), never hardcode it - Consider using
strategy="lazyOnload"for non-critical third-party scripts (chat widgets, surveys)
Font Loading
- Always use
next/font— it self-hosts fonts, eliminating external requests - Set
display: 'swap'— shows fallback text immediately, swaps to custom font when loaded - Limit font weights — only load weights you actually use (e.g. 400 and 700, not all 9 weights)
import { Inter } from 'next/font/google';
const inter = Inter({
subsets: ['latin', 'latin-ext'], // Include latin-ext for European languages
display: 'swap',
weight: ['400', '500', '700'], // Only weights actually used
});
Bundle Size
- Dynamic imports for below-fold components — product image gallery, reviews section, related products
- Avoid importing entire libraries —
import { format } from 'date-fns'notimport dayjs from 'dayjs'if only formatting - Analyze bundle — use
@next/bundle-analyzerto find oversized dependencies
// GOOD — dynamic import for below-fold content
import dynamic from 'next/dynamic';
const ProductReviews = dynamic(() => import('./ProductReviews'), {
loading: () => ,
});
const RelatedProducts = dynamic(() => import('./RelatedProducts'));
Streaming & Suspense
Use React Suspense to stream content progressively — the page shell renders immediately while slower data loads:
import { Suspense } from 'react';
export default function ProductPage({ params }: Props) {
return (
<>
{/* Renders immediately */}
}>
{/* Streams when ready */}
}>
{/* Streams when ready */}
);
}
Common Technical Anti-Patterns
- "use client" on data-display components — product cards, breadcrumbs, specs tables don't need client-side JS
- GTM with beforeInteractive — blocks rendering, destroys LCP
- Loading fonts via `` — use next/font instead
- Loading all font weights — only load what you use
- Synchronous data fetching in client components — fetch on the server, pass data as props
- Not using Suspense — causes the entire page to wait for the slowest data source
- Large dependencies for simple tasks — don't import moment.js for a date format
- Hardcoded GTM container IDs — use environment variables
Source & license
This open-source skill is cataloged on AgentStack and links to its original source — we do not rehost the code.
- Author: mvstepanek
- Source: mvstepanek/nextjs-ecommerce-seo-skills
- License: MIT
Install and usage instructions live in the source repository linked above.
Reviews
No reviews yet — be the first.
Write a review
Versions
- v0.1.0 Imported from the upstream source.