Building Notion Widgets - Exploring Next.js and Widget Development
Building Notion Widgets - Exploring Next.js and Widget Development
After diving into React with NotiTasks, I wanted to explore a different approach to web development. I decided to build Notion Widgets using Next.js to create lightweight, embeddable tools for Notion users while learning modern React patterns and optimization techniques.
The Learning Motivation
My decision to build Notion Widgets came from several goals:
Exploring Next.js Further
After working with Next.js on NotiTasks, I wanted to:
- Deepen my understanding of Next.js patterns
- Learn advanced React techniques
- Explore performance optimization
- Build embeddable components
Widget Development
I was curious about:
- How to create embeddable web components with React
- Cross-origin embedding challenges
- Performance optimization for widgets
- User experience in constrained environments
Notion Ecosystem
As a Notion power user, I wanted to:
- Enhance my own Notion workspace
- Create tools for the Notion community
- Learn about iframe embedding with React
- Understand Notion's embedding capabilities
The Technology Stack
Frontend
- Next.js 14: For the React framework with App Router
- React 18: For component development
- TailwindCSS: For utility-first styling
- Luxon: For advanced date/time handling
Deployment
- Vercel: For hosting and CDN
- GitHub: For version control
- Vercel Analytics: For usage tracking
The Widget Collection is growing
Date/Time Display
A simple but effective date and time display widget using React hooks:
import { useState, useEffect } from 'react'
import { DateTime } from 'luxon'
const DateTimeWidget = ({ format = 'full' }) => {
const [currentTime, setCurrentTime] = useState(DateTime.now())
useEffect(() => {
const timer = setInterval(() => {
setCurrentTime(DateTime.now())
}, 1000)
return () => clearInterval(timer)
}, [])
const formatTime = () => {
switch (format) {
case 'time':
return currentTime.toFormat('HH:mm:ss')
case 'date':
return currentTime.toFormat('EEEE, MMMM d, yyyy')
case 'full':
default:
return currentTime.toFormat('EEEE, MMMM d, yyyy HH:mm:ss')
}
}
return (
<div className="datetime-widget">
<div className="time-display">
{formatTime()}
</div>
</div>
)
}
Pomodoro Timer
A customizable Pomodoro timer for productivity:
import { useState, useEffect } from 'react'
const PomodoroWidget = ({ workTime = 25, breakTime = 5 }) => {
const [timeLeft, setTimeLeft] = useState(workTime * 60)
const [isRunning, setIsRunning] = useState(false)
const [isBreak, setIsBreak] = useState(false)
useEffect(() => {
let timer = null
if (isRunning && timeLeft > 0) {
timer = setInterval(() => {
setTimeLeft(prev => {
if (prev <= 1) {
// Timer finished, switch modes
setIsBreak(!isBreak)
return isBreak ? workTime * 60 : breakTime * 60
}
return prev - 1
})
}, 1000)
}
return () => clearInterval(timer)
}, [isRunning, timeLeft, isBreak, workTime, breakTime])
const formatTime = (seconds) => {
const mins = Math.floor(seconds / 60)
const secs = seconds % 60
return `${mins.toString().padStart(2, '0')}:${secs.toString().padStart(2, '0')}`
}
const toggleTimer = () => {
setIsRunning(!isRunning)
}
const resetTimer = () => {
setIsRunning(false)
setIsBreak(false)
setTimeLeft(workTime * 60)
}
return (
<div className="pomodoro-widget">
<div className="timer-display">
<div className="mode-indicator">
{isBreak ? 'Break' : 'Work'}
</div>
<div className="time">
{formatTime(timeLeft)}
</div>
</div>
<div className="controls">
<button onClick={toggleTimer}>
{isRunning ? 'Pause' : 'Start'}
</button>
<button onClick={resetTimer}>Reset</button>
</div>
</div>
)
}
Technical Challenges and Solutions
Cross-Origin Embedding
One of the biggest challenges was making React components work when embedded in Notion:
// Handle different embedding contexts
const useEmbeddedContext = () => {
const [isEmbedded, setIsEmbedded] = useState(false)
const [isNotion, setIsNotion] = useState(false)
useEffect(() => {
const embedded = window !== window.top
const notion = window.location.hostname.includes('notion.so')
setIsEmbedded(embedded)
setIsNotion(notion)
if (embedded) {
document.body.classList.add('embedded')
// Handle communication with parent frame
window.addEventListener('message', (event) => {
if (event.origin !== 'https://notion.so') return
// Handle messages from Notion
})
}
}, [])
return { isEmbedded, isNotion }
}
Performance Optimization
Since widgets are embedded, performance is crucial:
// Lazy loading for widgets
import { Suspense, lazy } from 'react'
const LazyWidget = lazy(() => import('./Widget'))
const WidgetContainer = () => (
<Suspense fallback={<div>Loading...</div>}>
<LazyWidget />
</Suspense>
)
Responsive Design with TailwindCSS
Widgets need to work in various container sizes:
const WidgetWrapper = ({ children }) => (
<div className="w-full max-w-md min-h-[200px] mx-auto p-4 rounded-lg bg-white shadow-md hover:shadow-lg transition-shadow">
{children}
</div>
)
Learning Outcomes
Next.js Mastery
I gained deep understanding of:
- App Router: File-based routing in Next.js 13+
- Server Components: Understanding React Server Components
- API Routes: Building backend functionality
- Performance: Optimization techniques for Next.js
- Deployment: Vercel integration and optimization
React Advanced Patterns
I learned about:
- Custom Hooks: Creating reusable logic
- Context API: State management for widgets
- Error Boundaries: Handling errors gracefully
- Memoization: Performance optimization with useMemo and useCallback
Widget Development
Understanding:
- Iframe Communication: Cross-origin messaging with React
- Embedding Best Practices: Making React components embeddable
- CSS Isolation: Preventing style conflicts with TailwindCSS
- Mobile Optimization: Touch-friendly interfaces
Notion Integration
Understanding:
- Embedding Limitations: What works in Notion iframes
- User Experience: How users interact with embedded React components
- Performance Requirements: Fast loading in constrained environments
The Impact on My Development Skills
Next.js Ecosystem
Building with Next.js taught me:
- How modern React frameworks work
- The importance of performance optimization
- When to use different rendering strategies
- Deployment and hosting best practices
User Experience
I learned to design for:
- Constrained environments (iframes)
- Various screen sizes with TailwindCSS
- Different embedding contexts
- Performance-conscious users
Development Workflow
The project improved my:
- Code organization with Next.js structure
- Testing strategies for React components
- Deployment processes with Vercel
- Documentation practices
Technical Implementation Details
Next.js App Router Structure
app/
widgets/
datetime/
page.tsx
pomodoro/
page.tsx
globals.css
layout.tsx
TailwindCSS Configuration
// tailwind.config.js
module.exports = {
content: [
'./pages/**/*.{js,ts,jsx,tsx,mdx}',
'./components/**/*.{js,ts,jsx,tsx,mdx}',
'./app/**/*.{js,ts,jsx,tsx,mdx}',
],
theme: {
extend: {
colors: {
widget: {
primary: '#007bff',
secondary: '#6c757d'
}
}
}
}
}
Community Impact
The project has gained traction in the open-source community:
- 7 stars and 12 forks on GitHub
- 6 contributors from the community
- Featured in Hacktoberfest 2024
- Used by Notion users worldwide
Future Enhancements
I'm planning to add:
- More Widget Types: Calendar, calculator, note-taking
- Customization Options: Themes, colors, sizes
- Data Persistence: Local storage for user preferences
- Analytics: Usage tracking with Vercel Analytics
- API Integration: More third-party services
Lessons Learned
Technology Choices
- Next.js: Excellent for modern, performant web applications
- TailwindCSS: Perfect for rapid prototyping and consistent design
- React: Powerful for component-based development
- Vercel: Excellent for Next.js deployment and hosting
Development Process
- Start Simple: Begin with basic functionality
- Test in Context: Always test embedded behavior
- Optimize for Performance: Every millisecond counts
- Document Embedding: Clear instructions for users
The Bigger Picture
Building Notion Widgets with Next.js taught me that modern React frameworks are incredibly powerful for creating embeddable, performant applications. The combination of Next.js, React, and TailwindCSS makes it possible to build sophisticated widgets quickly and efficiently.
Conclusion
Notion Widgets represents my exploration of Next.js and modern React development. It's not just a collection of tools - it's a testament to the power of modern web frameworks and the importance of understanding both the framework and the fundamentals.
The project has made me a more versatile developer by teaching me advanced React patterns, Next.js optimization, and the challenges of building embeddable components. It's also given me a deeper appreciation for the Notion ecosystem and the power of modern web technologies.
Try the widgets at notion-with-widgets.vercel.app and see how Next.js can create powerful, embeddable tools that enhance your Notion workspace.