FeaturesHow it worksPricingBlog
All articles
Guide2025-05-15 · 11 min read

How to Embed YouTube Videos in Next.js/React with EmbedShorts

If you're building a modern web application with Next.js or React, EmbedShorts is the simplest way to add dynamic YouTube video feeds without bloating your bundle or managing complex video infrastructure.

The EmbedShorts widget integrates seamlessly with your React codebase, works with Next.js server-side rendering, and adds zero dependencies to your project. Just a lightweight script tag and you're done.

Why Use EmbedShorts in Next.js/React?

  • Zero dependencies: No npm packages, no bloat, no conflicts
  • Lightweight: <15 KB minified widget
  • Next.js optimized: Works with SSR, SSG, and ISR
  • React-agnostic: Inject anywhere, no wrapper components needed
  • Auto-updates: Videos pull from YouTube automatically
  • Easy to customize: Control colors, layout, and behavior
  • Works out of the box: No build configuration needed

What You'll Need

  1. A Next.js or React project (any version from v16+)
  2. EmbedShorts account (free to start)
  3. A YouTube channel or playlist with videos to showcase

Step 1: Create Your EmbedShorts Widget

  1. Go to embedshorts.com and sign up
  2. Click "Create New Widget"
  3. Select content type:
    • Shorts: TikTok/Reels-style vertical videos
    • Videos: Longer-form YouTube content
  4. Choose a template:
    • Minimal: Clean, modern
    • Dark: Bold, professional
    • Reels: Creator-focused
    • Featured: Main video + sidebar
  5. Configure:
    • Source: YouTube Channel or Playlist
    • Paste your YouTube URL or playlist ID
    • Set max videos (5-10 recommended)
    • Choose orientation (portrait or landscape)
  6. Customize styling:
    • Accent color (match your brand)
    • Background color (match your theme)
    • Border radius, card gap
  7. Publish and get your widget ID

Step 2: Get Your Widget ID and Code

When you publish, you'll get:

<script src="https://embed.embedshorts.com/widget.js" data-widget-id="YOUR_WIDGET_ID"></script>
<div id="embedshorts-YOUR_WIDGET_ID"></div>

Save your widget ID (the part after data-widget-id=)

Step 3: Add to Your Next.js/React App

Method 1: Using Next.js Script Component (Recommended for Next.js)

The cleanest approach for Next.js apps:

// app/page.js (App Router) or pages/index.js (Pages Router)
import Script from 'next/script';

export default function Home() {
  const widgetId = 'YOUR_WIDGET_ID';

  return (
    <main>
      <h1>Our Latest Videos</h1>
      
      {/* Load EmbedShorts widget script */}
      <Script
        src="https://embed.embedshorts.com/widget.js"
        strategy="afterInteractive"
        onLoad={() => {
          // Optional: Widget is ready
        }}
      />
      
      {/* Container for the widget */}
      <div id={`embedshorts-${widgetId}`}></div>
      
      {/* Alternatively, use data attribute */}
      {/* <div data-widget-id={widgetId}></div> */}
    </main>
  );
}

Why this approach:

  • Optimized for Next.js performance
  • Loads after page interactive
  • No hydration mismatches
  • Follows Next.js best practices

Method 2: Using React useEffect (Universal React)

Works with any React setup:

import { useEffect } from 'react';

export default function VideoFeed() {
  const widgetId = 'YOUR_WIDGET_ID';

  useEffect(() => {
    // Load the EmbedShorts script if not already loaded
    if (!window.embedShortsLoaded) {
      const script = document.createElement('script');
      script.src = 'https://embed.embedshorts.com/widget.js';
      script.async = true;
      script.onload = () => {
        window.embedShortsLoaded = true;
      };
      document.body.appendChild(script);
    }
  }, []);

  return (
    <div>
      <h2>Latest Videos</h2>
      <div id={`embedshorts-${widgetId}`}></div>
    </div>
  );
}

Method 3: Create a Reusable Component

Make it clean and reusable:

// components/EmbedShortsWidget.jsx
import { useEffect } from 'react';

export default function EmbedShortsWidget({ 
  widgetId, 
  title = 'Videos' 
}) {
  useEffect(() => {
    // Load script once per component
    const script = document.createElement('script');
    script.src = 'https://embed.embedshorts.com/widget.js';
    script.async = true;
    document.body.appendChild(script);
  }, []);

  return (
    <section className="video-section">
      {title && <h2>{title}</h2>}
      <div id={`embedshorts-${widgetId}`} />
    </section>
  );
}

Usage:

// In your page component
import EmbedShortsWidget from '@/components/EmbedShortsWidget';

export default function Home() {
  return (
    <main>
      <EmbedShortsWidget 
        widgetId="YOUR_WIDGET_ID"
        title="Our Latest Videos"
      />
    </main>
  );
}

Method 4: Add to Next.js Layout (All Pages)

To display videos across your entire site:

// app/layout.js
import Script from 'next/script';

export default function RootLayout({ children }) {
  return (
    <html>
      <body>
        {children}
        
        {/* Load EmbedShorts globally */}
        <Script
          src="https://embed.embedshorts.com/widget.js"
          strategy="afterInteractive"
        />
      </body>
    </html>
  );
}

Then use the widget anywhere:

export default function Page() {
  return <div id="embedshorts-YOUR_WIDGET_ID"></div>;
}

Integration Examples

Homepage with Featured Video

// app/page.js
import Script from 'next/script';
import EmbedShortsWidget from '@/components/EmbedShortsWidget';

export default function Home() {
  return (
    <>
      <header>
        <h1>Welcome to Our Site</h1>
        <p>Check out our latest content below</p>
      </header>

      <Script
        src="https://embed.embedshorts.com/widget.js"
        strategy="afterInteractive"
      />

      <section className="featured-videos">
        <EmbedShortsWidget 
          widgetId="widget-home-featured"
          title="Featured Videos"
        />
      </section>

      <section className="rest-of-content">
        {/* Other content */}
      </section>
    </>
  );
}

Blog Post with Embedded Videos

// app/blog/[slug]/page.js
import Script from 'next/script';

export default function BlogPost({ post }) {
  return (
    <article>
      <h1>{post.title}</h1>
      <div className="post-content" dangerouslySetInnerHTML={{ __html: post.content }} />
      
      <Script
        src="https://embed.embedshorts.com/widget.js"
        strategy="afterInteractive"
      />

      {post.topic === 'tutorials' && (
        <aside className="related-videos">
          <h3>Video Tutorials</h3>
          <div id="embedshorts-widget-tutorials"></div>
        </aside>
      )}
    </article>
  );
}

Multiple Widgets on One Page

export default function GalleryPage() {
  const widgets = [
    { id: 'widget-1', title: 'Portfolio' },
    { id: 'widget-2', title: 'Client Work' },
    { id: 'widget-3', title: 'Behind the Scenes' },
  ];

  return (
    <>
      <Script src="https://embed.embedshorts.com/widget.js" strategy="afterInteractive" />
      
      <main className="gallery">
        {widgets.map(widget => (
          <section key={widget.id}>
            <h2>{widget.title}</h2>
            <div id={`embedshorts-${widget.id}`}></div>
          </section>
        ))}
      </main>
    </>
  );
}

Styling the Widget

EmbedShorts uses Shadow DOM (isolated styling), but you can still customize:

Tailwind CSS Example

export default function Home() {
  return (
    <div className="max-w-4xl mx-auto my-12">
      <h2 className="text-3xl font-bold mb-8">Latest Videos</h2>
      
      <div 
        id="embedshorts-YOUR_WIDGET_ID"
        className="rounded-lg shadow-lg overflow-hidden"
      />
    </div>
  );
}

CSS Modules Example

/* styles/VideoSection.module.css */
.videoContainer {
  max-width: 1200px;
  margin: 40px auto;
  padding: 0 20px;
  border-radius: 8px;
  box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
}

@media (max-width: 768px) {
  .videoContainer {
    margin: 20px auto;
    padding: 0 10px;
  }
}
import styles from '@/styles/VideoSection.module.css';

export default function VideoSection() {
  return (
    <div className={styles.videoContainer}>
      <div id="embedshorts-YOUR_WIDGET_ID"></div>
    </div>
  );
}

Environment Variables

Store your widget ID securely:

# .env.local
NEXT_PUBLIC_EMBEDSHORTS_WIDGET_ID=YOUR_WIDGET_ID
// Use in your component
export default function Home() {
  const widgetId = process.env.NEXT_PUBLIC_EMBEDSHORTS_WIDGET_ID;
  
  return <div id={`embedshorts-${widgetId}`}></div>;
}

Advanced: Dynamic Widget Loading

Load different widgets based on conditions:

'use client'; // Client component

import { useEffect } from 'react';

export default function DynamicWidget({ category }) {
  const widgetMap = {
    tutorials: 'widget-tutorials',
    portfolio: 'widget-portfolio',
    testimonials: 'widget-testimonials',
  };

  useEffect(() => {
    const script = document.createElement('script');
    script.src = 'https://embed.embedshorts.com/widget.js';
    script.async = true;
    document.body.appendChild(script);
  }, []);

  return (
    <div id={`embedshorts-${widgetMap[category]}`} />
  );
}

Performance Optimization

Code Splitting

Load widget only when needed:

import dynamic from 'next/dynamic';

const VideoFeed = dynamic(() => import('@/components/VideoFeed'), {
  loading: () => <div>Loading videos...</div>,
  ssr: false, // Don't render on server
});

export default function Home() {
  return (
    <>
      <header>Content here</header>
      <VideoFeed />
    </>
  );
}

Lazy Loading Videos

Videos only load when scrolled into view:

export default function Home() {
  return (
    <>
      <header>Content</header>
      
      {/* This div only loads widget script when visible */}
      <div id="embedshorts-YOUR_WIDGET_ID" />
    </>
  );
}

Image Optimization

If showing custom thumbnails:

import Image from 'next/image';

export default function VideoCard() {
  return (
    <Image
      src="/video-thumb.jpg"
      alt="Video thumbnail"
      width={400}
      height={225}
      priority={false}
    />
  );
}

TypeScript Support

If using TypeScript:

// types/embedshorts.d.ts
declare global {
  interface Window {
    embedShortsLoaded?: boolean;
  }
}

export {};
// components/EmbedShortsWidget.tsx
import { useEffect } from 'react';

interface EmbedShortsWidgetProps {
  widgetId: string;
  title?: string;
}

export default function EmbedShortsWidget({ 
  widgetId, 
  title 
}: EmbedShortsWidgetProps) {
  useEffect(() => {
    if (!window.embedShortsLoaded) {
      const script = document.createElement('script');
      script.src = 'https://embed.embedshorts.com/widget.js';
      script.async = true;
      script.onload = () => {
        window.embedShortsLoaded = true;
      };
      document.body.appendChild(script);
    }
  }, []);

  return (
    <section>
      {title && <h2>{title}</h2>}
      <div id={`embedshorts-${widgetId}`} />
    </section>
  );
}

Troubleshooting

Widget not rendering?

  • Verify widget ID is correct
  • Check that YouTube channel is public
  • Ensure script loaded (check network tab)
  • Check browser console for errors

Hydration mismatch in Next.js?

  • Use dynamic import with ssr: false
  • Or wrap in useEffect to load client-side only
  • Avoid rendering on server

Script loading multiple times?

  • Check your useEffect dependencies
  • Ensure script only loads once
  • Use Next.js Script component (handles this automatically)

Widget not responsive?

  • EmbedShorts adapts to its container
  • Ensure parent container is responsive
  • Set width: 100% on parent div
  • Test on actual mobile device

Styling conflicts?

  • EmbedShorts uses Shadow DOM (isolated)
  • Most conflicts shouldn't occur
  • Use CSS modules to avoid global conflicts
  • Contact support if issues persist

Bundle Size Impact

EmbedShorts is tiny:

  • Widget JS: 14 KB minified
  • No dependencies: Zero npm packages
  • Total impact: <15 KB (0.01 MB)
  • Lazy loads: Only loads when widget appears on page

Your Next.js bundle won't be affected.

API Integration (Advanced)

If you want to build custom UI:

interface EmbedShortsAPI {
  videos: Video[];
  settings: WidgetSettings;
  channel_url: string;
}

async function fetchEmbedShortsData(widgetId: string): Promise<EmbedShortsAPI> {
  const response = await fetch(`https://api.embedshorts.com/embed/${widgetId}`);
  return response.json();
}

Contact support for full API documentation.

Pricing & Next Steps

EmbedShorts is free to start:

  • 1 widget
  • 1,000 monthly views
  • Full customization

Upgrade as you scale:

  • Starter: $19/mo (3 widgets, 10K views)
  • Pro: $99/mo (10 widgets, 100K views)
  • Agency: $499/mo (unlimited, white-label)

Ready to add video to your Next.js/React app?

  1. Sign up for free
  2. Create your widget
  3. Copy the widget ID
  4. Add to your app using Script component or useEffect
  5. Deploy

FAQs

Does it work with Next.js 14+? Yes, fully compatible with all recent Next.js versions.

Can I use it in App Router and Pages Router? Yes, both work perfectly.

Will it break my build? No. It's just a script tag, no build configuration needed.

Can I customize the widget? Yes, fully customizable in the EmbedShorts dashboard.

Does it work with TypeScript? Absolutely. Include type definitions if desired.

What about SSR/SSG? Works fine. Widget renders on client-side automatically.

Can I use multiple widgets? Yes, create separate widgets for different sections.


Integrate video into your Next.js/React app today. Create your free EmbedShorts account and start embedding in minutes.

Ready to embed YouTube videos?

Free plan available. No credit card. 5-minute setup.

Start for free →