diff --git a/README.md b/README.md index 46f2c08..7a6ed1b 100644 --- a/README.md +++ b/README.md @@ -11,9 +11,61 @@ Recent updates include upgrading to Next.js 16 with Turbopack, migrating to Cont - **Runtime**: React 19 - **Styling**: Tailwind CSS + Typography plugin - **Content**: Markdown via Contentlayer2 (`contentlayer2/source-files`) +- **Search**: Pagefind for full-text search - **Theming**: `next-themes` (light/dark), env‑driven accent color system - **Content source**: Git submodule `content` → [`personal-blog`](https://gitea.gbanyan.net/gbanyan/personal-blog.git) +## Performance Optimizations + +This blog is optimized for performance using Next.js 16 features and best practices: + +### Next.js 16 Features + +- **Partial Prerendering (PPR)** enabled via `cacheComponents: true` for faster page loads +- **Turbopack** enabled in development for 4-5x faster builds +- **Static site generation** for all blog posts and pages +- **Loading states** and error boundaries for better UX + +### Bundle Size Reduction + +- **CSS-only animations** replacing Framer Motion (~50KB reduction) +- **Dynamic imports** for SearchModal component (lazy loaded when needed) +- **Optimized scroll reveals** using IntersectionObserver instead of React state +- **Tree-shaking** with Next.js compiler removing unused code + +### Image & Video Optimization + +- **Responsive images** with proper `sizes` attributes for all Next.js Image components +- **Lazy loading** for below-fold images, priority loading for hero images +- **AVIF/WebP formats** for better compression +- **GIF to video conversion**: Large animated GIFs converted to MP4/WebM for 80-95% file size reduction + - `AddNewThings3.gif` (2.4MB) → WebM (116KB) = 95% reduction + - `Things3.gif` (1.5MB) → WebM (170KB) = 89% reduction + +### SEO & Social Media + +- **Dynamic OG image generation** using `@vercel/og` +- **Enhanced metadata** with OpenGraph and Twitter Cards for all posts +- **1200x630 social images** with post title, description, and tags + +### Search Optimization + +Pagefind is configured to index only essential content: +- **Indexed**: Post titles, tags, and article body content +- **Excluded**: Navigation, related posts, footer, and UI elements +- This improves search relevance and reduces index size + +Configuration in `app/blog/[slug]/page.tsx`: +- `data-pagefind-body` wraps main content area +- `data-pagefind-meta="tags"` marks tags as metadata +- `data-pagefind-ignore` excludes navigation and related posts + +### Caching Strategy + +- **Static assets** cached for 1 year (`max-age=31536000, immutable`) +- **PPR** caches static shells while streaming dynamic content +- **Font optimization** with Next.js font loading + ## Project Structure - `app/` – Next.js App Router diff --git a/app/blog/[slug]/page.tsx b/app/blog/[slug]/page.tsx index 5ac571b..2c8a7ca 100644 --- a/app/blog/[slug]/page.tsx +++ b/app/blog/[slug]/page.tsx @@ -81,9 +81,11 @@ export default async function BlogPostPage({ params }: Props) {
- - -
+ {/* Main content area for Pagefind indexing */} +
+ + +
{post.published_at && (

{new Date(post.published_at).toLocaleDateString( @@ -95,7 +97,7 @@ export default async function BlogPostPage({ params }: Props) { {post.title} {post.tags && ( -

+
{post.tags.map((t) => ( +
- - - - - - - {relatedPosts.length > 0 && ( + {/* Exclude navigation and related posts from search indexing */} +
-
+ + + + + {relatedPosts.length > 0 && ( + + +

相關文章 @@ -166,7 +171,8 @@ export default async function BlogPostPage({ params }: Props) {

- )} + )} +
diff --git a/components/optimized-video.tsx b/components/optimized-video.tsx new file mode 100644 index 0000000..90b46d6 --- /dev/null +++ b/components/optimized-video.tsx @@ -0,0 +1,68 @@ +import { HTMLAttributes } from 'react'; +import clsx from 'clsx'; + +interface OptimizedVideoProps extends Omit, 'src'> { + src: string; + alt?: string; + width?: number; + height?: number; + autoPlay?: boolean; + loop?: boolean; + muted?: boolean; + playsInline?: boolean; + controls?: boolean; + poster?: string; +} + +/** + * Optimized video component that provides: + * - Multiple format support (WebM and MP4) for better browser compatibility + * - Proper accessibility attributes + * - Automatic GIF-like behavior when autoPlay is enabled + * - Lightweight alternative to GIF files with 80-95% file size reduction + */ +export function OptimizedVideo({ + src, + alt, + width, + height, + autoPlay = true, + loop = true, + muted = true, + playsInline = true, + controls = false, + poster, + className, + ...props +}: OptimizedVideoProps) { + // Remove file extension to get base path + const basePath = src.replace(/\.(mp4|webm|gif)$/i, ''); + + return ( + + ); +} diff --git a/content b/content index c728118..58c056f 160000 --- a/content +++ b/content @@ -1 +1 @@ -Subproject commit c728118ba1820fc10d34b9522cb8519d692ba627 +Subproject commit 58c056fcf0fc7313530ac989cfa3ac1ba85ce29b diff --git a/next-env.d.ts b/next-env.d.ts index 9edff1c..c4b7818 100644 --- a/next-env.d.ts +++ b/next-env.d.ts @@ -1,6 +1,6 @@ /// /// -import "./.next/types/routes.d.ts"; +import "./.next/dev/types/routes.d.ts"; // NOTE: This file should not be edited // see https://nextjs.org/docs/app/api-reference/config/typescript for more information.