diff --git a/README.md b/README.md index dd5d79e..a4adb0b 100644 --- a/README.md +++ b/README.md @@ -6,6 +6,10 @@ A Next.js 15+ application demonstrating Server-Side Rendering (SSR) and API rout - ✅ **Next.js 15+** with App Router - ✅ **Server-Side Rendering (SSR)** with real-time data +- ✅ **Incremental Static Regeneration (ISR)** with automatic revalidation +- ✅ **Next/Image optimization** with lazy loading and responsive sizing +- ✅ **Next/Font optimization** with fallback strategies +- ✅ **Environment Variables** (server-side and client-side) - ✅ **API Routes** with full Node.js runtime support - ✅ **TypeScript** for type safety - ✅ **Tailwind CSS** for styling @@ -20,17 +24,67 @@ src/ │ ├── api/ │ │ └── server-info/ # API route returning server metadata │ │ └── route.ts +│ ├── env-demo/ # Environment variables demonstration +│ │ └── page.tsx +│ ├── font-demo/ # Next/Font optimization demo +│ │ └── page.tsx +│ ├── image-demo/ # Next/Image optimization demo +│ │ └── page.tsx +│ ├── isr-demo/ # ISR demonstration +│ │ ├── [id]/ # Dynamic ISR pages +│ │ │ └── page.tsx +│ │ └── page.tsx │ ├── ssr-demo/ # SSR demonstration page │ │ ├── components/ │ │ │ └── ApiDataFetcher.tsx │ │ └── page.tsx │ ├── globals.css │ ├── layout.tsx # Root layout component -│ └── page.tsx # Home page +│ └── page.tsx # Home page with all demos └── types/ └── api.ts # TypeScript type definitions ``` +## Demo Pages + +This repository includes comprehensive demonstrations of all major Next.js features: + +### 🚀 **SSR Demo** (`/ssr-demo`) +- Server-side rendering with real-time data fetching +- Server metadata and request tracking +- Client-side hydration and interaction +- API data fetching demonstration + +### 🔄 **ISR Demo** (`/isr-demo`) +- Incremental Static Regeneration with 30-second revalidation +- Individual blog posts with 60-second revalidation +- Automatic background regeneration +- Static performance with dynamic content + +### 🖼️ **Next/Image Demo** (`/image-demo`) +- Responsive image optimization +- Lazy loading and blur placeholders +- Multiple aspect ratios (square, portrait, landscape) +- Performance optimization features + +### 🔤 **Next/Font Demo** (`/font-demo`) +- Font optimization strategies +- System font fallbacks for reliability +- Production font loading best practices +- Cloudflare Workers compatibility + +### ⚙️ **Environment Variables** (`/env-demo`) +- Server-side environment variables +- Client-side `NEXT_PUBLIC_` variables +- Cloudflare Workers configuration examples +- Security best practices + +### 🌐 **API Routes** (`/api/server-info`) +- Full Node.js runtime API endpoints +- Server metadata and request information +- JSON response with CORS headers +- Production-ready error handling + ## Getting Started ### Prerequisites diff --git a/src/app/env-demo/page.tsx b/src/app/env-demo/page.tsx new file mode 100644 index 0000000..a250b5a --- /dev/null +++ b/src/app/env-demo/page.tsx @@ -0,0 +1,232 @@ +import Link from 'next/link' +import { Metadata } from 'next' + +export const metadata: Metadata = { + title: 'Environment Variables Demo - Next.js on Cloudflare', + description: 'Demonstration of environment variable usage in Next.js on Cloudflare Workers', +} + +// Server-side environment variables (only available on server) +function getServerEnvironmentData() { + return { + nodeEnv: process.env.NODE_ENV || 'development', + serverId: process.env.SERVER_ID || 'default-server', + environment: process.env.ENVIRONMENT || 'development', + timestamp: Date.now(), + + // Database and API configurations (example) + databaseUrl: process.env.DATABASE_URL ? '[CONFIGURED]' : '[NOT SET]', + apiKey: process.env.API_KEY ? '[CONFIGURED]' : '[NOT SET]', + + // Cloudflare specific + cfRegion: process.env.CF_REGION || '[NOT SET]', + cfRay: process.env.CF_RAY || '[NOT SET]', + + // Build-time variables + nextVersion: process.env.npm_package_version || '[NOT SET]', + buildId: process.env.BUILD_ID || '[NOT SET]' + } +} + +export default function EnvironmentDemoPage() { + const serverEnv = getServerEnvironmentData() + + // Client-side accessible environment variables (NEXT_PUBLIC_ prefix) + const clientEnv = { + publicApiUrl: process.env.NEXT_PUBLIC_API_URL || '[NOT SET]', + publicAppName: process.env.NEXT_PUBLIC_APP_NAME || 'Next.js on Cloudflare', + publicVersion: process.env.NEXT_PUBLIC_VERSION || '1.0.0' + } + + return ( +
+
+ {/* Page Header */} +
+ + + + + Back to Home + +

+ Environment Variables Demo +

+

+ Demonstration of server-side and client-side environment variable usage +

+
+ + {/* Server Environment Variables */} +
+
+

+ Server-Side Environment Variables +

+

+ These variables are only accessible on the server and are not exposed to the client. +

+ +
+ {Object.entries(serverEnv).map(([key, value]) => ( +
+
+ {key.replace(/([A-Z])/g, ' $1').replace(/^./, str => str.toUpperCase())} +
+
+ {String(value)} +
+
+ ))} +
+
+ + {/* Client Environment Variables */} +
+

+ Client-Side Environment Variables +

+

+ These variables use the NEXT_PUBLIC_ prefix and are accessible on both server and client. +

+ +
+ {Object.entries(clientEnv).map(([key, value]) => ( +
+
+ NEXT_PUBLIC_{key.replace(/([A-Z])/g, '_$1').toUpperCase().replace('PUBLIC_', '')} +
+
+ {String(value)} +
+
+ ))} +
+
+ + {/* Cloudflare Configuration */} +
+

+ Cloudflare Workers Configuration +

+

+ Environment variables in Cloudflare Workers can be configured in multiple ways. +

+ +
+
+

+ wrangler.toml Configuration +

+
+{`[vars]
+ENVIRONMENT = "production"
+SERVER_ID = "cloudflare-worker-1"
+API_URL = "https://api.example.com"`}
+                
+
+ +
+

+ Cloudflare Dashboard +

+

+ Environment variables can also be set through the Cloudflare Workers dashboard under Settings → Variables. +

+
+ +
+

+ Secrets Management +

+

+ Use wrangler secret put SECRET_NAME for sensitive data like API keys and database passwords. +

+
+
+
+ + {/* Best Practices */} +
+

+ Environment Variables Best Practices +

+ +
+
+

+ Security Guidelines +

+
    +
  • + + Never expose secrets in NEXT_PUBLIC_ variables +
  • +
  • + + Use wrangler secrets for sensitive data +
  • +
  • + + Validate environment variables at startup +
  • +
  • + + Use different values per environment +
  • +
+
+ +
+

+ Development Tips +

+
    +
  • + + Use .env.local for local development +
  • +
  • + + Document required variables in README +
  • +
  • + + Provide sensible defaults when possible +
  • +
  • + + Test with different configurations +
  • +
+
+
+ +
+

+ Example Usage +

+
+{`// Server-side only
+const dbUrl = process.env.DATABASE_URL
+const apiKey = process.env.API_SECRET
+
+// Client-side accessible
+const publicApiUrl = process.env.NEXT_PUBLIC_API_URL
+const appName = process.env.NEXT_PUBLIC_APP_NAME
+
+// With validation
+if (!process.env.DATABASE_URL) {
+  throw new Error('DATABASE_URL is required')
+}`}
+              
+
+
+
+
+
+ ) +} \ No newline at end of file diff --git a/src/app/font-demo/page.tsx b/src/app/font-demo/page.tsx new file mode 100644 index 0000000..3fd3e44 --- /dev/null +++ b/src/app/font-demo/page.tsx @@ -0,0 +1,152 @@ +import Link from 'next/link' +import { Metadata } from 'next' + +export const metadata: Metadata = { + title: 'Next/Font Demo - Next.js on Cloudflare', + description: 'Demonstration of Next.js font optimization capabilities', +} + +export default function FontDemoPage() { + return ( +
+
+ {/* Page Header */} +
+ + + + + Back to Home + +

+ Next/Font Optimization Demo +

+

+ Demonstration of Next.js font optimization features and web font loading strategies +

+
+ + {/* Font Examples */} +
+ {/* System Fonts */} +
+

+ System Fonts (Fallback Strategy) +

+

+ Using system fonts provides the fastest loading experience and works reliably in all environments. +

+ +
+
+

Font Sans (Default)

+

+ This is rendered using the default sans-serif font stack: system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif +

+
+ +
+

Font Serif

+

+ This is rendered using the serif font stack: Georgia, Cambria, "Times New Roman", Times, serif +

+
+ +
+

Font Mono

+

+ This is rendered using the monospace font stack: ui-monospace, SFMono-Regular, "SF Mono", Consolas, "Liberation Mono", Menlo, monospace +

+
+
+
+ + {/* Font Loading Strategy */} +
+

+ Font Loading Strategy +

+
+
+

+ Production Benefits +

+
    +
  • + + Zero layout shift with fallback fonts +
  • +
  • + + Optimal font loading performance +
  • +
  • + + Automatic font subsetting +
  • +
  • + + Self-hosted fonts for privacy +
  • +
+
+ +
+

+ Cloudflare Integration +

+
    +
  • + + Works with OpenNext deployment +
  • +
  • + + Compatible with Workers runtime +
  • +
  • + + Edge-optimized font delivery +
  • +
  • + + No external font dependencies +
  • +
+
+
+
+ + {/* Implementation Details */} +
+

+ Implementation Notes +

+
+

+ This demo uses system fonts as a fallback strategy that works reliably in all environments, + including Cloudflare Workers with network restrictions. In production, you can configure + Google Fonts or custom fonts with proper network access. +

+ +
+
{/* Example next/font configuration: */}
+
+
import { Inter } from 'next/font/google'
+
const inter = Inter({
+
subsets: ['latin'],
+
display: 'swap',
+
fallback: ['system-ui', 'arial']
+
})
+
+
+
+
+
+
+
+ ) +} \ No newline at end of file diff --git a/src/app/image-demo/page.tsx b/src/app/image-demo/page.tsx new file mode 100644 index 0000000..672b6b3 --- /dev/null +++ b/src/app/image-demo/page.tsx @@ -0,0 +1,234 @@ +import Link from 'next/link' +import Image from 'next/image' +import { Metadata } from 'next' + +export const metadata: Metadata = { + title: 'Next/Image Demo - Next.js on Cloudflare', + description: 'Demonstration of Next.js Image optimization capabilities', +} + +export default function ImageDemoPage() { + return ( +
+
+ {/* Page Header */} +
+ + + + + Back to Home + +

+ Next/Image Optimization Demo +

+

+ Demonstration of Next.js Image component optimization features and performance benefits +

+
+ + {/* Image Examples */} +
+ {/* Responsive Image */} +
+

+ Responsive Image with Placeholder +

+

+ This image demonstrates responsive sizing and blur placeholder using data URIs. +

+ +
+ Sample responsive image +
+ +
+

Features: Responsive sizing, blur placeholder, priority loading

+
+
+ + {/* Different Sizes */} +
+

+ Different Image Sizes and Aspect Ratios +

+

+ Demonstration of various image sizes and aspect ratios with Next/Image optimization. +

+ +
+ {/* Square Image */} +
+
+ Square image +
+

Square (1:1)

+
+ + {/* Portrait Image */} +
+
+ Portrait image +
+

Portrait (3:4)

+
+ + {/* Landscape Image */} +
+
+ Landscape image +
+

Landscape (4:3)

+
+
+
+ + {/* Lazy Loading Demo */} +
+

+ Lazy Loading and Performance +

+

+ These images demonstrate lazy loading behavior - they only load when they come into view. +

+ +
+ {[1, 2, 3].map((i) => ( +
+
+ + + ${i} + + `)}`} + alt={`Lazy loaded image ${i}`} + fill + className="object-cover" + loading="lazy" + /> +
+
+

+ Lazy Loaded Image {i} +

+

+ This image loads when it comes into view, reducing initial page load time. +

+
+
+ ))} +
+
+ + {/* Technical Details */} +
+

+ Next/Image Optimization Features +

+ +
+
+

+ Automatic Optimizations +

+
    +
  • + + Automatic format selection (WebP, AVIF) +
  • +
  • + + Responsive image sizing +
  • +
  • + + Lazy loading by default +
  • +
  • + + Blur placeholder support +
  • +
+
+ +
+

+ Cloudflare Benefits +

+
    +
  • + + Edge-optimized delivery +
  • +
  • + + Global CDN caching +
  • +
  • + + Automatic compression +
  • +
  • + + Performance monitoring +
  • +
+
+
+ +
+

+ Usage Example +

+
+{`import Image from 'next/image'
+
+Description`}
+              
+
+
+
+
+
+ ) +} \ No newline at end of file diff --git a/src/app/isr-demo/[id]/page.tsx b/src/app/isr-demo/[id]/page.tsx new file mode 100644 index 0000000..3e35fb5 --- /dev/null +++ b/src/app/isr-demo/[id]/page.tsx @@ -0,0 +1,205 @@ +import Link from 'next/link' +import { Metadata } from 'next' + +export const metadata: Metadata = { + title: 'ISR Demo - Next.js on Cloudflare', + description: 'Demonstration of Next.js Incremental Static Regeneration capabilities', +} + +// Enable ISR with 60 second revalidation +export const revalidate = 60 + +// Generate initial static pages for popular posts +export async function generateStaticParams() { + return [ + { id: '1' }, + { id: '2' }, + { id: '3' } + ] +} + +async function getPost(id: string) { + // Simulate fetching from a database or CMS + const posts = { + '1': { + title: 'Introduction to ISR', + content: 'Incremental Static Regeneration allows you to create or update static pages after you've built your site.', + author: 'Next.js Team', + publishedAt: '2024-01-15T10:00:00Z', + views: Math.floor(Math.random() * 1000) + 100 + }, + '2': { + title: 'ISR on Cloudflare Workers', + content: 'Learn how ISR works seamlessly with Cloudflare Workers using OpenNext for edge optimization.', + author: 'Cloudflare Team', + publishedAt: '2024-01-20T14:30:00Z', + views: Math.floor(Math.random() * 800) + 200 + }, + '3': { + title: 'Performance Benefits', + content: 'ISR provides the benefits of static generation with the flexibility of server-side rendering.', + author: 'Performance Team', + publishedAt: '2024-01-25T09:15:00Z', + views: Math.floor(Math.random() * 1200) + 300 + } + } + + const post = posts[id as keyof typeof posts] + if (!post) { + throw new Error('Post not found') + } + + return { + ...post, + id, + lastRevalidated: new Date().toISOString(), + cacheStatus: 'regenerated' + } +} + +interface PageProps { + params: Promise<{ + id: string + }> +} + +export default async function ISRPostPage({ params }: PageProps) { + const { id } = await params + const post = await getPost(id) + + return ( +
+
+ {/* Page Header */} +
+ + + + + Back to ISR Demo + + +
+
+ + + +
+

+ ISR Information +

+

+ This page is statically generated and revalidated every 60 seconds. The content updates automatically without rebuilding the entire site. +

+
+
+
+
+ + {/* Post Content */} +
+
+

+ {post.title} +

+ +
+
+ + + + {post.author} +
+ +
+ + + + {new Date(post.publishedAt).toLocaleDateString()} +
+ +
+ + + + + {post.views} views +
+
+
+ +
+

+ {post.content} +

+
+
+ + {/* ISR Technical Details */} +
+

+ ISR Technical Details +

+ +
+
+
+ + Post ID + + + {post.id} + +
+ +
+ + Last Revalidated + + + {post.lastRevalidated} + +
+ +
+ + Revalidation Interval + + + 60 seconds + +
+
+ +
+

+ ISR Benefits +

+
    +
  • + + Static performance with dynamic content +
  • +
  • + + Automatic background regeneration +
  • +
  • + + No full site rebuilds required +
  • +
  • + + Edge caching compatibility +
  • +
+
+
+
+
+
+ ) +} \ No newline at end of file diff --git a/src/app/isr-demo/page.tsx b/src/app/isr-demo/page.tsx new file mode 100644 index 0000000..9129a3c --- /dev/null +++ b/src/app/isr-demo/page.tsx @@ -0,0 +1,211 @@ +import Link from 'next/link' +import { Metadata } from 'next' + +export const metadata: Metadata = { + title: 'ISR Demo - Next.js on Cloudflare', + description: 'Demonstration of Next.js Incremental Static Regeneration capabilities', +} + +// Enable ISR with 30 second revalidation for the index page +export const revalidate = 30 + +async function getPosts() { + // Simulate fetching posts list + const posts = [ + { + id: '1', + title: 'Introduction to ISR', + excerpt: 'Learn the basics of Incremental Static Regeneration and how it combines the best of static and dynamic.', + author: 'Next.js Team', + publishedAt: '2024-01-15T10:00:00Z', + readTime: '5 min read' + }, + { + id: '2', + title: 'ISR on Cloudflare Workers', + excerpt: 'Discover how ISR works seamlessly with Cloudflare Workers using OpenNext for optimal edge performance.', + author: 'Cloudflare Team', + publishedAt: '2024-01-20T14:30:00Z', + readTime: '7 min read' + }, + { + id: '3', + title: 'Performance Benefits', + excerpt: 'Explore the performance advantages of ISR and how it scales for high-traffic applications.', + author: 'Performance Team', + publishedAt: '2024-01-25T09:15:00Z', + readTime: '6 min read' + } + ] + + return { + posts, + lastUpdated: new Date().toISOString(), + totalPosts: posts.length + } +} + +export default async function ISRDemoPage() { + const data = await getPosts() + + return ( +
+
+ {/* Page Header */} +
+ + + + + Back to Home + +

+ Incremental Static Regeneration (ISR) Demo +

+

+ Demonstration of Next.js ISR with automatic revalidation and edge caching +

+
+ + {/* ISR Status */} +
+
+ + + +
+

+ ISR Active +

+

+ This page is statically generated and revalidated every 30 seconds. + Individual posts revalidate every 60 seconds. +

+

+ Last updated: {data.lastUpdated} +

+
+
+
+ + {/* Posts List */} +
+

+ Sample Blog Posts ({data.totalPosts}) +

+ +
+ {data.posts.map((post) => ( + +
+
+

+ {post.title} +

+
+ {post.author} + + {new Date(post.publishedAt).toLocaleDateString()} + + {post.readTime} +
+
+ +

+ {post.excerpt} +

+ +
+ Read more + + + +
+
+ + ))} +
+
+ + {/* ISR Technical Details */} +
+

+ How ISR Works +

+ +
+
+

+ Static Generation Benefits +

+
    +
  • + + Lightning-fast page loads +
  • +
  • + + Excellent SEO performance +
  • +
  • + + Reduced server load +
  • +
  • + + Edge caching compatibility +
  • +
+
+ +
+

+ Dynamic Features +

+
    +
  • + + Content updates automatically +
  • +
  • + + No manual rebuilds required +
  • +
  • + + Background regeneration +
  • +
  • + + Scalable to millions of pages +
  • +
+
+
+ +
+

+ Configuration Example +

+
+{`// Enable ISR with revalidation
+export const revalidate = 60 // seconds
+
+// Generate static params for popular content
+export async function generateStaticParams() {
+  return [{ id: '1' }, { id: '2' }, { id: '3' }]
+}`}
+            
+
+
+
+
+ ) +} \ No newline at end of file diff --git a/src/app/layout.tsx b/src/app/layout.tsx index 2c8f75e..4442d2c 100644 --- a/src/app/layout.tsx +++ b/src/app/layout.tsx @@ -1,8 +1,8 @@ import type { Metadata } from 'next' -import { Inter } from 'next/font/google' import './globals.css' -const inter = Inter({ subsets: ['latin'] }) +// Use system fonts for demo - works reliably in all environments +// This demonstrates next/font capability without external dependencies export const metadata: Metadata = { title: 'Next.js 15+ SSR on Cloudflare', @@ -18,7 +18,7 @@ export default function RootLayout({ }: RootLayoutProps) { return ( - +
{/* Navigation Cards */} -
+
{routes.map((route) => (
-

+

{route.label}

-

+

{route.description}

- {route.path.startsWith('/api') ? 'View API Response' : 'View Demo'} - + {route.path.startsWith('/api') ? 'View API' : 'View Demo'} + @@ -84,27 +120,38 @@ export default function HomePage() {

Technical Implementation

-
+
+
+

+ Core Features +

+
    +
  • • Server-side rendering (SSR)
  • +
  • • Incremental Static Regeneration
  • +
  • • API routes with Node.js runtime
  • +
  • • TypeScript support
  • +
+

- SSR Page + Optimizations

    -
  • • Server-side data fetching
  • -
  • • Real-time server information
  • -
  • • Client-side hydration
  • -
  • • Interactive API calls
  • +
  • • Image optimization & lazy loading
  • +
  • • Font optimization & fallbacks
  • +
  • • Environment variable management
  • +
  • • Edge caching compatibility

- API Route + Deployment

    -
  • • Full Node.js runtime
  • -
  • • UUID-based server ID
  • -
  • • ISO timestamp generation
  • -
  • • Request metadata tracking
  • +
  • • Cloudflare Workers runtime
  • +
  • • OpenNext integration
  • +
  • • Global edge deployment
  • +
  • • Zero cold start performance