Skip to content

Commit 0856287

Browse files
Og cards (#830)
* complete og cards * fix errors * prevent uneeded files from being uploaded to vercel * solves issue for mdx pages * update to solve 250 MB error
1 parent 2cb91b6 commit 0856287

File tree

11 files changed

+836
-20
lines changed

11 files changed

+836
-20
lines changed

.vercelignore

Lines changed: 39 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,41 @@
1+
# Git and version control
12
.git
3+
.github
4+
5+
# Build cache
6+
.next/cache
7+
.cache
8+
9+
# Reports and testing
210
reports
3-
.next/cache
11+
coverage
12+
__tests__
13+
14+
# Documentation (except README and data files)
15+
docs/
16+
*.md
17+
!README.md
18+
!src/data/**/*.md
19+
!src/data/**/*.mdx
20+
21+
# Development files
22+
.vscode
23+
.idea
24+
25+
# Logs
26+
*.log
27+
logs
28+
29+
# Temporary files
30+
tmp
31+
temp
32+
33+
# Test files
34+
*.test.ts
35+
*.test.tsx
36+
*.test.js
37+
*.test.jsx
38+
*.spec.ts
39+
*.spec.tsx
40+
*.spec.js
41+
*.spec.jsx

URL_PREVIEW_CARD_README.md

Lines changed: 308 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,308 @@
1+
# URL Preview Card Feature
2+
3+
This feature allows you to create beautiful preview cards for any URL shared in your application. It automatically fetches Open Graph metadata (title, description, image) from the URL and displays it in a card format.
4+
5+
## Features
6+
7+
- **Automatic metadata extraction**: Fetches Open Graph tags, Twitter Card data, and fallback metadata from any URL
8+
- **Autogenerated images**: Creates a fallback image for URLs that don't have Open Graph images
9+
- **Loading states**: Shows a skeleton loader while fetching metadata
10+
- **Error handling**: Gracefully handles failed requests with error messages
11+
- **Responsive design**: Works on all screen sizes
12+
- **Hover effects**: Interactive animations on hover
13+
- **External link safety**: Opens links in new tabs with `rel="noopener noreferrer"`
14+
15+
## Files Created
16+
17+
### 1. Type Definitions
18+
`/src/types/url-metadata.ts`
19+
- `URLMetadata`: Interface for URL metadata (title, description, image, etc.)
20+
- `OGFetchResponse`: API response interface
21+
22+
### 2. API Routes
23+
24+
#### `/api/og/fetch`
25+
Fetches metadata from a given URL by parsing HTML and extracting Open Graph tags.
26+
27+
**Request:**
28+
```typescript
29+
POST /api/og/fetch
30+
Content-Type: application/json
31+
32+
{
33+
"url": "https://example.com"
34+
}
35+
```
36+
37+
**Response:**
38+
```typescript
39+
{
40+
"success": true,
41+
"data": {
42+
"url": "https://example.com",
43+
"title": "Example Domain",
44+
"description": "Example description",
45+
"image": "https://example.com/og-image.jpg",
46+
"siteName": "Example",
47+
"favicon": "https://example.com/favicon.ico",
48+
"type": "website"
49+
}
50+
}
51+
```
52+
53+
#### Fallback Image Generation
54+
For URLs without OG images, the component automatically generates a unique SVG image client-side using the `generateFallbackImage()` utility function. This approach:
55+
- Creates unique colors based on hostname (same site = same colors)
56+
- Shows the first letter of the domain
57+
- Includes VWC branding
58+
- **No API calls required** - fully client-side generation
59+
- **Zero bundle size impact** - just inline SVG generation
60+
61+
### 3. Components
62+
63+
#### `URLPreviewCard`
64+
`/src/components/url-preview-card/index.tsx`
65+
66+
A React component that displays a preview card for a URL.
67+
68+
**Props:**
69+
- `url` (string, required): The URL to preview
70+
- `className` (string, optional): Additional CSS classes
71+
72+
**Usage:**
73+
```tsx
74+
import URLPreviewCard from '@/components/url-preview-card';
75+
76+
function MyComponent() {
77+
return (
78+
<URLPreviewCard url="https://vetswhocode.io" />
79+
);
80+
}
81+
```
82+
83+
### 4. Demo Page
84+
`/src/pages/url-preview-demo.tsx`
85+
86+
A demo page that showcases the URL preview card functionality. Visit `/url-preview-demo` to test it.
87+
88+
## Usage Examples
89+
90+
### Basic Usage
91+
```tsx
92+
import URLPreviewCard from '@/components/url-preview-card';
93+
94+
export default function MyPage() {
95+
return (
96+
<div>
97+
<h1>Check out this link:</h1>
98+
<URLPreviewCard url="https://github.com/Vets-Who-Code" />
99+
</div>
100+
);
101+
}
102+
```
103+
104+
### Multiple Cards
105+
```tsx
106+
import URLPreviewCard from '@/components/url-preview-card';
107+
108+
export default function LinkCollection() {
109+
const urls = [
110+
'https://vetswhocode.io',
111+
'https://github.com/Vets-Who-Code',
112+
'https://www.npmjs.com/package/next',
113+
];
114+
115+
return (
116+
<div className="tw-grid tw-grid-cols-1 md:tw-grid-cols-2 lg:tw-grid-cols-3 tw-gap-4">
117+
{urls.map((url) => (
118+
<URLPreviewCard key={url} url={url} />
119+
))}
120+
</div>
121+
);
122+
}
123+
```
124+
125+
### With Custom Styling
126+
```tsx
127+
import URLPreviewCard from '@/components/url-preview-card';
128+
129+
export default function StyledCard() {
130+
return (
131+
<URLPreviewCard
132+
url="https://vetswhocode.io"
133+
className="tw-max-w-md tw-mx-auto tw-shadow-2xl"
134+
/>
135+
);
136+
}
137+
```
138+
139+
### Using the API Directly
140+
If you need just the metadata without the card component:
141+
142+
```tsx
143+
import { useState, useEffect } from 'react';
144+
import type { URLMetadata } from '@/types/url-metadata';
145+
146+
export default function CustomCard() {
147+
const [metadata, setMetadata] = useState<URLMetadata | null>(null);
148+
149+
useEffect(() => {
150+
fetch('/api/og/fetch', {
151+
method: 'POST',
152+
headers: { 'Content-Type': 'application/json' },
153+
body: JSON.stringify({ url: 'https://vetswhocode.io' }),
154+
})
155+
.then(res => res.json())
156+
.then(data => {
157+
if (data.success) {
158+
setMetadata(data.data);
159+
}
160+
});
161+
}, []);
162+
163+
if (!metadata) return <div>Loading...</div>;
164+
165+
return (
166+
<div>
167+
<h2>{metadata.title}</h2>
168+
<p>{metadata.description}</p>
169+
<img src={metadata.image} alt={metadata.title} />
170+
</div>
171+
);
172+
}
173+
```
174+
175+
## How It Works
176+
177+
1. **User provides a URL**: The URL is passed to the `URLPreviewCard` component
178+
2. **Fetch metadata**: The component calls `/api/og/fetch` with the URL
179+
3. **Parse HTML**: The API route fetches the URL and parses the HTML with node-html-parser
180+
4. **Extract metadata**: Open Graph tags, Twitter Card data, and fallback metadata are extracted
181+
5. **Return data**: The metadata is returned to the component
182+
6. **Display card**: The component renders a card with the title, description, and image
183+
7. **Fallback image**: If no image is found, a unique SVG is generated client-side based on the hostname
184+
185+
## Customization
186+
187+
### Styling
188+
The component uses Tailwind CSS with the `tw-` prefix. You can customize the appearance by:
189+
- Modifying the component in `/src/components/url-preview-card/index.tsx`
190+
- Passing custom classes via the `className` prop
191+
- Updating the Tailwind config
192+
193+
### Image Generation
194+
Customize the fallback OG image appearance in the `generateFallbackImage()` function in `/src/components/url-preview-card/index.tsx`:
195+
- Change colors, gradients, typography (lines 22-42)
196+
- Modify the SVG layout and design
197+
- Add your logo or different branding
198+
- Adjust the hash function for different color schemes
199+
200+
### Metadata Extraction
201+
Enhance metadata extraction in `/src/pages/api/og/fetch.ts`:
202+
- Add more metadata sources (Schema.org, JSON-LD, etc.)
203+
- Implement caching to reduce API calls
204+
- Add support for additional metadata fields
205+
206+
## Performance Optimization
207+
208+
### Caching (Recommended)
209+
To avoid fetching the same URL multiple times, consider implementing caching:
210+
211+
1. **Client-side caching**: Use React Query or SWR
212+
2. **Server-side caching**: Store metadata in Redis or your database
213+
3. **CDN caching**: Cache the API responses at the edge
214+
215+
Example with Prisma (database caching):
216+
```typescript
217+
// Add to your Prisma schema
218+
model URLMetadata {
219+
id String @id @default(cuid())
220+
url String @unique
221+
title String?
222+
description String?
223+
image String?
224+
siteName String?
225+
favicon String?
226+
type String?
227+
createdAt DateTime @default(now())
228+
updatedAt DateTime @updatedAt
229+
}
230+
```
231+
232+
## Testing
233+
234+
Visit `/url-preview-demo` to test the feature:
235+
1. Enter any URL
236+
2. Click "Preview"
237+
3. See the generated card
238+
239+
Try these example URLs:
240+
- https://vetswhocode.io
241+
- https://github.com/Vets-Who-Code
242+
- https://www.npmjs.com/package/next
243+
244+
## Dependencies
245+
246+
- `node-html-parser`: Lightweight HTML parsing library for Node.js
247+
- **No additional dependencies** for image generation - uses inline SVG generation
248+
249+
## Browser Support
250+
251+
Works in all modern browsers that support:
252+
- ES6+
253+
- Fetch API
254+
- Next.js Image component
255+
256+
## Security Considerations
257+
258+
- **SSRF Protection**: The API validates URLs before fetching
259+
- **Timeout**: Requests timeout after 10 seconds to prevent hanging
260+
- **User-Agent**: A custom user-agent is set to identify the bot
261+
- **Content-Type validation**: Only HTML content is parsed
262+
- **XSS Protection**: Metadata is sanitized before rendering
263+
264+
## Troubleshooting
265+
266+
### No image appears
267+
- Check if the URL has Open Graph meta tags
268+
- Verify the image URL is accessible
269+
- Check browser console for CORS errors
270+
- The fallback image generator should handle this automatically
271+
272+
### Slow loading
273+
- The URL might be slow to respond
274+
- Consider implementing caching
275+
- Check your network connection
276+
277+
### CORS errors
278+
- OG image fetching happens server-side to avoid CORS issues
279+
- If you see CORS errors, ensure you're using the API route
280+
281+
### Build errors
282+
- Ensure all dependencies are installed: `npm install`
283+
- Check TypeScript errors: `npm run typecheck`
284+
- Clear Next.js cache: `rm -rf .next`
285+
286+
## Future Enhancements
287+
288+
Potential improvements:
289+
- [ ] Add database caching for fetched metadata
290+
- [ ] Implement rate limiting to prevent abuse
291+
- [ ] Add support for video OG tags
292+
- [ ] Create multiple card layout variants
293+
- [ ] Add copy-to-clipboard functionality for URLs
294+
- [ ] Implement a URL shortener integration
295+
- [ ] Add social share buttons to cards
296+
- [ ] Support for rich embeds (YouTube, Twitter, etc.)
297+
298+
## Contributing
299+
300+
To contribute to this feature:
301+
1. Create a new branch from `og_cards`
302+
2. Make your changes
303+
3. Test thoroughly
304+
4. Submit a pull request
305+
306+
---
307+
308+
Built with ❤️ for Vets Who Code

next.config.js

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,11 @@ const nextConfig = {
2626

2727
experimental: {},
2828

29+
// Ensure MDX and data files are included for all pages
30+
outputFileTracingIncludes: {
31+
'/**': ['src/data/**/*'],
32+
},
33+
2934
webpack(config, { isServer }) {
3035
config.module.rules.push({
3136
test: /\.svg$/,
@@ -38,12 +43,28 @@ const nextConfig = {
3843
};
3944
}
4045

46+
// Optimize for serverless functions
47+
if (isServer) {
48+
// Enable tree-shaking for server bundles
49+
config.optimization = {
50+
...config.optimization,
51+
usedExports: true,
52+
sideEffects: false,
53+
};
54+
}
55+
4156
return config;
4257
},
4358

4459
images: {
4560
domains: [],
46-
remotePatterns: [],
61+
remotePatterns: [
62+
{
63+
protocol: 'https',
64+
hostname: '**',
65+
},
66+
],
67+
unoptimized: false,
4768
},
4869
};
4970

0 commit comments

Comments
 (0)