Skip to content

Commit 8dd4611

Browse files
committed
feat: Introduce a comprehensive demo component showcasing blur, color, and client-side image placeholders via a new server action.
1 parent d2bdc37 commit 8dd4611

8 files changed

Lines changed: 412 additions & 74 deletions

File tree

README.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,11 @@ To engage with this project locally:
3636

3737
We welcome contributions! Please see our [CONTRIBUTING.md](./CONTRIBUTING.md) for details on how to get started.
3838

39+
## Acknowledgements
40+
41+
- [sharp](https://sharp.pixelplumbing.com/) - High performance Node.js image processing
42+
- [fast-average-color-node](https://github.com/fast-average-color/fast-average-color-node) - A simple library that calculates the average color of an image
43+
3944
## License
4045

4146
This project is licensed under the MIT License - see the [LICENSE](./LICENSE) file for details.

apps/web/next.config.ts

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,14 @@
11
import type { NextConfig } from "next";
22

33
const nextConfig: NextConfig = {
4-
/* config options here */
4+
images: {
5+
remotePatterns: [
6+
{
7+
protocol: 'https',
8+
hostname: 'images.unsplash.com',
9+
},
10+
],
11+
},
512
};
613

714
export default nextConfig;

apps/web/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
},
1212
"dependencies": {
1313
"clsx": "^2.1.1",
14+
"framer-motion": "^12.29.0",
1415
"lucide-react": "^0.563.0",
1516
"next": "16.1.4",
1617
"next-image-placeholder": "workspace:*",

apps/web/src/app/actions.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
"use server";
2+
3+
import { getPlaceholder } from "next-image-placeholder";
4+
5+
export async function getPlaceholderAction(url: string) {
6+
return getPlaceholder(url);
7+
}

apps/web/src/app/page.tsx

Lines changed: 85 additions & 73 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,79 @@
11
import { StripePanels } from "@/components/stripe-panels";
2+
import { DemoTabs } from "@/components/demo-tabs";
23
import { ComponentPreview } from "@/components/component-preview";
34
import Image from "next/image";
5+
import { getPlaceholder } from "next-image-placeholder";
6+
import { codeToHtml } from "shiki";
7+
8+
const DEMO_IMAGE = "https://images.unsplash.com/photo-1619441207978-3d326c46e2c9?q=80&w=2669&auto=format&fit=crop&ixlib=rb-4.1.0&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D";
9+
10+
export default async function Home() {
11+
const initialData = await getPlaceholder(DEMO_IMAGE);
12+
13+
const blurCode = `// Server Component
14+
const { base64 } = await getPlaceholder(
15+
"${DEMO_IMAGE}"
16+
);
17+
18+
// Render
19+
<Image
20+
src="${DEMO_IMAGE}"
21+
placeholder="blur"
22+
blurDataURL={base64}
23+
alt="Demo"
24+
/>`;
25+
26+
const colorCode = `// Server Component
27+
const color = await getColor(
28+
"${DEMO_IMAGE}"
29+
);
30+
31+
// Render
32+
<div style={{ backgroundColor: color }}>
33+
<Image src="..." />
34+
</div>`;
35+
36+
const clientCode = `// 1. Create Server Action (app/actions.ts)
37+
'use server'
38+
import { getPlaceholder } from 'next-image-placeholder'
39+
40+
export async function getPlaceholderAction(imageUrl: string) {
41+
return getPlaceholder(imageUrl)
42+
}
43+
44+
// 2. Use the component (Recommended)
45+
'use client'
46+
import { PlaceholderImage } from 'next-image-placeholder/react'
47+
import { getPlaceholderAction } from './actions'
48+
49+
export function Gallery({ src }: { src: string }) {
50+
return (
51+
<PlaceholderImage
52+
src={src}
53+
action={getPlaceholderAction}
54+
width={400}
55+
height={300}
56+
alt="Gallery Image"
57+
className="rounded-lg"
58+
/>
59+
)
60+
}`;
61+
62+
const codeSnippets = {
63+
blur: {
64+
code: blurCode,
65+
html: await codeToHtml(blurCode, { lang: 'tsx', theme: 'github-dark-dimmed' })
66+
},
67+
color: {
68+
code: colorCode,
69+
html: await codeToHtml(colorCode, { lang: 'tsx', theme: 'github-dark-dimmed' })
70+
},
71+
client: {
72+
code: clientCode,
73+
html: await codeToHtml(clientCode, { lang: 'tsx', theme: 'github-dark-dimmed' })
74+
}
75+
};
476

5-
export default function Home() {
677
return (
778
<div className="relative min-h-screen flex flex-col items-center overflow-x-hidden">
879
<StripePanels />
@@ -12,16 +83,16 @@ export default function Home() {
1283
<div className="h-px w-full bg-neutral-200 dark:bg-neutral-800" />
1384

1485
{/* Hero Section */}
15-
<div className="flex flex-col items-center justify-center pt-32 pb-24 px-6 border-b border-neutral-200 dark:border-neutral-800">
16-
<div className="space-y-8 text-center max-w-4xl">
86+
<div className="flex flex-col items-center justify-center pt-20 pb-0 px-6">
87+
<div className="space-y-6 text-center max-w-4xl">
1788
<div className="inline-flex items-center px-3 py-1 rounded-full border border-neutral-200 dark:border-neutral-800 bg-neutral-50 dark:bg-neutral-900 text-xs font-medium text-neutral-600 dark:text-neutral-400 mb-4">
1889
v1.1.2 is now available
1990
</div>
2091

2192
<h1 className="text-4xl sm:text-5xl md:text-7xl font-bold tracking-tight text-neutral-900 dark:text-neutral-50">
22-
Image Placeholders,<br />
93+
next image<br />
2394
<span className="text-transparent bg-clip-text bg-gradient-to-r from-neutral-500 to-neutral-800 dark:from-neutral-400 dark:to-neutral-100">
24-
Perfected.
95+
placeholder
2596
</span>
2697
</h1>
2798

@@ -30,7 +101,7 @@ export default function Home() {
30101
Zero client-side JavaScript for generation.
31102
</p>
32103

33-
<div className="flex flex-col sm:flex-row gap-4 justify-center pt-8">
104+
<div className="flex flex-col sm:flex-row gap-4 justify-center pt-6">
34105
<a
35106
href="/docs"
36107
className="px-6 py-3 rounded-full bg-neutral-900 dark:bg-neutral-50 text-neutral-50 dark:text-neutral-900 font-medium text-sm hover:opacity-90 transition-opacity text-center"
@@ -49,74 +120,15 @@ export default function Home() {
49120
</div>
50121
</div>
51122

52-
{/* Feature Grid */}
53-
<div className="grid grid-cols-1 md:grid-cols-3 divide-y md:divide-y-0 md:divide-x divide-neutral-200 dark:divide-neutral-800 border-b border-neutral-200 dark:border-neutral-800">
54-
{/* Card 1 */}
55-
<div className="p-12 relative group">
56-
<div className="absolute top-0 left-0 w-full h-1 bg-gradient-to-r from-indigo-500 to-purple-500 opacity-0 group-hover:opacity-100 transition-opacity" />
57-
<h3 className="text-lg font-semibold mb-3 text-neutral-900 dark:text-neutral-50">Server-Side Optimisation</h3>
58-
<p className="text-neutral-500 dark:text-neutral-400 text-sm leading-relaxed">
59-
Generate placeholders at build time or request time on the server. No client-side waterfall.
60-
</p>
123+
{/* Live Demo Section - Show, Don't Tell */}
124+
<div className="divide-y divide-neutral-200 dark:divide-neutral-800 border-b border-neutral-200 dark:border-neutral-800">
125+
<div className="px-8 pb-12 pt-12 lg:px-12 lg:pb-12 lg:pt-12">
126+
<DemoTabs
127+
initialData={initialData}
128+
sourceUrl={DEMO_IMAGE}
129+
codeSnippets={codeSnippets}
130+
/>
61131
</div>
62-
63-
{/* Card 2 */}
64-
<div className="p-12 relative group">
65-
<div className="absolute top-0 left-0 w-full h-1 bg-gradient-to-r from-blue-500 to-cyan-500 opacity-0 group-hover:opacity-100 transition-opacity" />
66-
<h3 className="text-lg font-semibold mb-3 text-neutral-900 dark:text-neutral-50">Dominant Color</h3>
67-
<p className="text-neutral-500 dark:text-neutral-400 text-sm leading-relaxed">
68-
Automatically extract legitimate dominant colors from images for a smooth loading experience.
69-
</p>
70-
</div>
71-
72-
{/* Card 3 */}
73-
<div className="p-12 relative group">
74-
<div className="absolute top-0 left-0 w-full h-1 bg-gradient-to-r from-emerald-500 to-green-500 opacity-0 group-hover:opacity-100 transition-opacity" />
75-
<h3 className="text-lg font-semibold mb-3 text-neutral-900 dark:text-neutral-50">Typesafe & Lightweight</h3>
76-
<p className="text-neutral-500 dark:text-neutral-400 text-sm leading-relaxed">
77-
Built with TypeScript. Minimal dependencies. Designed for modern React Server Components.
78-
</p>
79-
</div>
80-
</div>
81-
82-
{/* Demo Section */}
83-
<div className="p-12 border-b border-neutral-200 dark:border-neutral-800">
84-
<div className="text-center mb-16">
85-
<h2 className="text-3xl font-bold mb-4 text-neutral-900 dark:text-neutral-50">See it in action</h2>
86-
<p className="text-neutral-500 dark:text-neutral-400 max-w-xl mx-auto">
87-
The library generates blur data URLs that you can pass directly to the Next.js Image component.
88-
</p>
89-
</div>
90-
91-
<ComponentPreview
92-
code={`import Image from "next/image";
93-
import { getPlaceholder } from "next-image-placeholder";
94-
95-
export default async function Page() {
96-
const { blurDataURL } = await getPlaceholder(
97-
"https://images.unsplash.com/photo-1579546929518-9e396f3cc809"
98-
);
99-
100-
return (
101-
<Image
102-
src="https://images.unsplash.com/photo-1579546929518-9e396f3cc809"
103-
placeholder="blur"
104-
blurDataURL={blurDataURL}
105-
width={800}
106-
height={600}
107-
alt="Gradient"
108-
/>
109-
);
110-
}`}
111-
usage="npm install next-image-placeholder fast-average-color-node sharp"
112-
>
113-
<div className="relative w-full max-w-md aspect-[4/3] rounded-lg overflow-hidden shadow-fancy">
114-
{/* Mockup for preview since we can't run server actions in client component easily without setup */}
115-
<div className="w-full h-full bg-neutral-200 animate-pulse flex items-center justify-center text-neutral-400 text-sm">
116-
Image Preview
117-
</div>
118-
</div>
119-
</ComponentPreview>
120132
</div>
121133

122134
</main>

0 commit comments

Comments
 (0)