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
- A Next.js or React project (any version from v16+)
- EmbedShorts account (free to start)
- A YouTube channel or playlist with videos to showcase
Step 1: Create Your EmbedShorts Widget
- Go to embedshorts.com and sign up
- Click "Create New Widget"
- Select content type:
- Shorts: TikTok/Reels-style vertical videos
- Videos: Longer-form YouTube content
- Choose a template:
- Minimal: Clean, modern
- Dark: Bold, professional
- Reels: Creator-focused
- Featured: Main video + sidebar
- Configure:
- Source: YouTube Channel or Playlist
- Paste your YouTube URL or playlist ID
- Set max videos (5-10 recommended)
- Choose orientation (portrait or landscape)
- Customize styling:
- Accent color (match your brand)
- Background color (match your theme)
- Border radius, card gap
- 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
dynamicimport withssr: false - Or wrap in
useEffectto load client-side only - Avoid rendering on server
Script loading multiple times?
- Check your useEffect dependencies
- Ensure script only loads once
- Use Next.js
Scriptcomponent (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?
- Sign up for free
- Create your widget
- Copy the widget ID
- Add to your app using
Scriptcomponent oruseEffect - 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.