DEV Community

Anas Sheikh
Anas Sheikh

Posted on

7 Next.js 15 Mistakes I Made Building 4 Templates (And How to Fix Them)

I've built 4 production Next.js 15 templates in the last few months.

I made a lot of mistakes along the way.

Here are the ones that wasted the most of my time โ€” so you don't have to repeat them.


1. Forgetting suppressHydrationWarning with next-themes

This one took me hours to debug.

// ๐Ÿšซ This causes hydration errors
<html lang="en">
  <body>
    <ThemeProvider>{children}</ThemeProvider>
  </body>
</html>

// โœ… This fixes it
<html lang="en" suppressHydrationWarning>
  <body>
    <ThemeProvider attribute="class" defaultTheme="dark">
      {children}
    </ThemeProvider>
  </body>
</html>
Enter fullscreen mode Exit fullscreen mode

The suppressHydrationWarning on the html tag is not optional when using next-themes. Without it you get a hydration mismatch on every page load.


2. Using useEffect for Data That Should Be Server-Side

// ๐Ÿšซ Fetching on client when you don't need to
'use client';
export default function Page() {
  const [data, setData] = useState(null);

  useEffect(() => {
    fetch('/api/data').then(r => r.json()).then(setData);
  }, []);

  return <div>{data?.title}</div>;
}

// โœ… Fetch on server โ€” faster, simpler, better SEO
export default async function Page() {
  const data = await fetch('https://api.example.com/data').then(r => r.json());
  return <div>{data.title}</div>;
}
Enter fullscreen mode Exit fullscreen mode

If your data doesn't change based on user interaction โ€” fetch it on the server. Faster page load, better SEO, less JavaScript.


3. Not Using Next.js Image Component

// ๐Ÿšซ Regular img tag
<img src="/hero.jpg" alt="Hero" className="w-full" />

// โœ… Next.js Image โ€” automatic WebP, lazy loading, size optimization
import Image from 'next/image';

<Image
  src="/hero.jpg"
  alt="Hero"
  width={1200}
  height={630}
  priority // add this for above-the-fold images only
  className="w-full"
/>
Enter fullscreen mode Exit fullscreen mode

The priority prop is important โ€” only use it for images visible without scrolling. Using it everywhere defeats the purpose.


4. Wrong Font Loading

// ๐Ÿšซ This causes layout shift and slow loading
<link href="https://fonts.googleapis.com/css2?family=Inter" rel="stylesheet" />

// โœ… Use next/font โ€” zero layout shift, self-hosted automatically
import { Inter } from 'next/font/google';

const inter = Inter({
  subsets: ['latin'],
  display: 'swap', // prevents invisible text during load
});

export default function RootLayout({ children }) {
  return (
    <html className={inter.className}>
      {children}
    </html>
  );
}
Enter fullscreen mode Exit fullscreen mode

5. Client Components Too High in the Tree

// ๐Ÿšซ Making the whole page a client component for one button
'use client';
export default function Page() {
  const [open, setOpen] = useState(false);
  return (
    <div>
      <HeavyContent /> {/* This is now client-side JS */}
      <button onClick={() => setOpen(!open)}>Toggle</button>
    </div>
  );
}

// โœ… Push client state down โ€” keep the page as server component
export default function Page() {
  return (
    <div>
      <HeavyContent /> {/* Still server-rendered */}
      <ToggleButton /> {/* Only this is client */}
    </div>
  );
}

// Separate file
'use client';
export function ToggleButton() {
  const [open, setOpen] = useState(false);
  return <button onClick={() => setOpen(!open)}>Toggle</button>;
}
Enter fullscreen mode Exit fullscreen mode

6. Not Handling Loading States Properly

// ๐Ÿšซ Page just shows nothing while loading
export default async function Page() {
  const data = await slowFetch(); // user sees blank page
  return <div>{data.title}</div>;
}

// โœ… Use loading.tsx โ€” automatic Suspense boundary
// app/dashboard/loading.tsx
export default function Loading() {
  return (
    <div className="animate-pulse space-y-4">
      <div className="h-8 bg-slate-700 rounded w-1/3" />
      <div className="h-4 bg-slate-700 rounded w-2/3" />
      <div className="h-4 bg-slate-700 rounded w-1/2" />
    </div>
  );
}
Enter fullscreen mode Exit fullscreen mode

Just create a loading.tsx file next to your page.tsx โ€” Next.js handles the rest automatically.


7. Ignoring Metadata for SEO

// ๐Ÿšซ No metadata = bad SEO
export default function Page() {
  return <div>Content</div>;
}

// โœ… Export metadata from every page
import type { Metadata } from 'next';

export const metadata: Metadata = {
  title: 'Dashboard โ€” Your SaaS',
  description: 'Manage your AI SaaS with our premium dashboard.',
  openGraph: {
    title: 'Dashboard โ€” Your SaaS',
    description: 'Manage your AI SaaS with our premium dashboard.',
    images: ['/og-image.jpg'],
  },
};

export default function Page() {
  return <div>Content</div>;
}
Enter fullscreen mode Exit fullscreen mode

Every page needs its own metadata. The template from layout.tsx is a fallback, not a replacement.


Summary

Mistake Fix
Hydration errors with themes Add suppressHydrationWarning to <html>
Client fetching server data Use async server components
Regular img tags Use next/image
Google Fonts via link tag Use next/font/google
Client components too high Push state down to leaf components
No loading states Create loading.tsx files
Missing page metadata Export metadata from every page

I applied all of these while building my Next.js templates.

If you want to see them in a real production codebase:

Live demos:

Templates: https://pixelanas.gumroad.com

Drop your own Next.js mistakes in the comments ๐Ÿ‘‡


Anas โ€” full-stack Next.js developer. Building premium templates and web apps. X: @pixelanas

Top comments (0)