A simple React component to embed Live2D models (via live2d-widget) in Next.js projects.
- Updated default
baseUrlhost from the old GitHub username to2hjaito. - Updated repository links and badges to the new GitHub profile.
- Kept full compatibility for existing model paths ending with
/model.json.
Full history:
- English: CHANGELOG.md
- Vietnamese: CHANGELOG-vi.md
- 🧠 Auto-load Live2D Widget
- ⚙️ Zero-config usage with App Router
- 🎒 Comes with 35+ built-in models
- ✅ SSR-safe using
dynamic(() => import(...), { ssr: false }) - 🎲 Random model selection
- 🎨 Full customization (position, size, opacity, etc.)
- 📦 Custom base URL support (self-host models)
- 🔄 Loading state & error handling
- 💪 TypeScript support with exported types
- ⚡ React 18 & 19 compatible
npm install next-live2d🧩 Usage in Next.js (app/layout.tsx)
'use client'
import { Live2DWidget } from 'next-live2d'
import { ReactNode } from 'react'
import './globals.css'
export default function RootLayout({ children }: { children: ReactNode }) {
return (
<html lang="en">
<body>
<main>{children}</main>
<Live2DWidget modelName="mai" />
</body>
</html>
)
}To minimize runtime issues in production projects:
- Render
Live2DWidgetonly in Client Components. - Avoid rendering the widget from Server Components directly.
- Keep one widget instance per page/layout to avoid competing initializations.
- Prefer stable
modelNamevalues across frequent rerenders. - For custom model hosting, ensure
model.jsonand textures are accessible with correct CORS headers.
Recommended pattern for App Router:
'use client'
import { Live2DWidget } from 'next-live2d'
export default function Live2DClientWidget() {
return <Live2DWidget modelName="histoire" />
}<Live2DWidget
modelName="senko"
position="left"
width={200}
height={350}
opacity={0.9}
hoverOpacity={0.3}
/><Live2DWidget random /><Live2DWidget
modelName="my-model"
baseUrl="https://my-cdn.com/live2d-models"
/><Live2DWidget
modelName="histoire"
fallback={<div>Loading Live2D...</div>}
onLoad={() => console.log('Model loaded!')}
onError={(err) => console.error('Failed:', err)}
onClick={() => alert('You clicked the model!')}
/><Live2DWidget
modelName="senko"
className="bottom-0 right-0 fixed z-50 opacity-80"
style={{ width: 200, height: 300 }}
/>| Prop | Type | Default | Description |
|---|---|---|---|
modelName |
string |
'histoire' |
Name of the model folder (must include model.json) |
baseUrl |
string |
GitHub raw URL | Custom base URL to load models from |
position |
'left' | 'right' |
'right' |
Widget position on screen |
width |
number |
180 |
Widget width in pixels |
height |
number |
300 |
Widget height in pixels |
opacity |
number |
0.8 |
Default opacity (0-1) |
hoverOpacity |
number |
0.2 |
Opacity when hovering (0-1) |
showOnMobile |
boolean |
true |
Show widget on mobile devices |
random |
boolean |
false |
Pick a random built-in model |
className |
string |
- | Custom CSS/Tailwind classes |
style |
CSSProperties |
- | Inline styles |
fallback |
ReactNode |
- | Component to show while loading |
onLoad |
() => void |
- | Callback when model loads |
onError |
(error) => void |
- | Callback on load error |
onClick |
() => void |
- | Callback when widget is clicked |
- This project follows semantic versioning.
- Patch releases focus on stability and compatibility fixes.
- Minor releases add non-breaking features.
- Major releases may include behavior changes or migration notes.
import {
Live2DWidget,
Live2DWidgetProps,
ModelName,
BUILT_IN_MODELS,
getRandomModel
} from 'next-live2d';
// Get a random model name
const model: ModelName = getRandomModel();
// Access all built-in model names
console.log(BUILT_IN_MODELS); // ['histoire', 'bilibili-22', ...]The Live2D widget is rendered into a #live2d-widget DOM element, positioned as fixed by default.
If you pass className or style, they will override the default style.
By default, the widget looks for:
Trần Hữu Đang Website: https://dangth.dev
📝 License MIT





































