Next.js Icon

Next.js Blog System Setup Guide

Requirements:

Next.js 13+ with App Router
Tailwind CSS configured
TypeScript support

Copy the prompt below and paste it into your favorite AI code assistant (Github Copilot, Cursor, Claude Code, etc.). Make sure to use the agent mode to let the AI do all the work for you.

AI Prompt for Next.js
This comprehensive prompt includes all the requirements and implementation details for your Next.js blog system.
# Next.js Blog Implementation Guide

Implement a simple file-based blog system for a Next.js project with App Router. This guide provides complete step-by-step instructions to create a fully functional blog system without a database.

## Overview
Create a blog system where posts are added by simply dropping .tsx files in a "blogs" folder. The system will automatically generate an index of all posts and create routes for them.

## Features
- Blog listing page at /blog
- Individual blog post pages at /blog/[slug]
- Automatic sitemap generation
- Responsive design with Tailwind CSS
- SEO-friendly metadata

## Requirements
- Next.js 13+ with App Router
- Tailwind CSS configured
- TypeScript support

## Step 1: Install Required Dependencies
Use the preferred package manager for the project. Run the following command to install all necessary packages:
```bash
npm install lucide-react
npx shadcn@latest add card
```

## Step 2: Create Blog Listing Page
Create the directory `app/blog/` and add `page.tsx` with the following content:

```tsx
import Link from 'next/link'
import Image from 'next/image'
import { Card, CardContent, CardDescription, CardHeader, CardTitle, CardFooter } from '@/components/ui/card'
import { getBlogPosts, type BlogPost } from '@/lib/blog-utils'
import { blogMetadata } from '@/blogs'
import { ArrowRight } from 'lucide-react'

export default function BlogPage() {
  const posts = getBlogPosts()

  return (
    <div className="container mx-auto px-4 py-8">
      <div className="max-w-7xl mx-auto">
        <h1 className="text-4xl font-bold mb-8">Blog</h1>
        
        {posts.length === 0 ? (
          <p className="text-muted-foreground">No blog posts found.</p>
        ) : (
          <div className="grid gap-6 md:grid-cols-2 lg:grid-cols-3">
            {posts.map((post: BlogPost) => {
              const metadata = (blogMetadata as Record<string, any>)[post.slug] || {}
              return (
                <Link key={post.slug} href={`/blog/${post.slug}`}>
                  <Card className="flex flex-col gap-0 h-full hover:shadow-lg hover:shadow-blue-500/10 hover:border-blue-300 dark:hover:border-blue-600 transition-all duration-300 group cursor-pointer py-0 overflow-hidden">
                    {metadata.image && (
                      <div className="relative w-full h-48 overflow-hidden rounded-t-lg">
                        <Image
                          fill
                          style={{ objectFit: 'cover' }}
                          src={metadata.image}
                          alt={metadata.title || post.title}
                          className="transition-transform duration-300 group-hover:scale-105"
                        />
                      </div>
                    )}
                    <CardHeader className="p-3 md:p-6">
                      <CardTitle className="text-2xl line-clamp-3 pb-0.5 font-semibold leading-none tracking-tight">{post.title}</CardTitle>
                    </CardHeader>
                    <CardContent className="flex-1 p-3 pt-0 md:p-6 md:pt-0">
                      {post.description && (
                        <CardDescription className="text-sm line-clamp-3">
                          {post.description}
                        </CardDescription>
                      )}
                    </CardContent>
                    <CardFooter className="pt-0 p-3 md:p-6 md:pt-0">
                      <div className="flex items-center justify-between text-sm text-muted-foreground w-full">
                        <div className="flex items-center gap-2 md:gap-4 flex-wrap">
                          <div>
                            {post.date && new Date(post.date).toLocaleDateString()}
                          </div>
                          {metadata.readTimeMinutes && (
                            <div>
                              {metadata.readTimeMinutes} min read
                            </div>
                          )}
                        </div>
                        <div className="flex items-center gap-1 text-blue-600 dark:text-blue-400 opacity-100 md:opacity-0 md:group-hover:opacity-100 transition-opacity duration-200">
                          <span className="text-xs font-medium">Read more</span>
                          <ArrowRight className="h-3 w-3" />
                        </div>
                      </div>
                    </CardFooter>
                  </Card>
                </Link>
              )
            })}
          </div>
        )}
      </div>
    </div>
  )
}
```

## Step 3: Create Individual Blog Post Page
Create the directory `app/blog/[slug]/` and add `page.tsx` with the following content (includes "More Articles" section at the bottom):

```tsx
import { notFound } from 'next/navigation'
import type { Metadata } from 'next'
import Script from 'next/script'
import Link from 'next/link'
import Image from 'next/image'
import { ArrowLeft, ArrowRight, BookOpen } from 'lucide-react'
import { Card, CardContent, CardDescription, CardHeader, CardTitle, CardFooter } from '@/components/ui/card'
import { getBlogPost, getBlogPosts, type BlogPost } from '@/lib/blog-utils'
import { blogMetadata } from '@/blogs'

interface BlogPostPageProps {
  params: { slug: string }
}

export async function generateStaticParams() {
  const posts = getBlogPosts()
  return posts.map((post) => ({ slug: post.slug }))
}

export async function generateMetadata({ params }: BlogPostPageProps): Promise<Metadata> {
  const { slug } = await params
  const post = getBlogPost(slug)
  if (!post) return {}

  const meta = (blogMetadata as Record<string, any>)[slug] || {}
  const title = meta.title || post.title
  const description = meta.description || post.description || ''
  const image = meta.image || undefined
  const published = meta.createdAt || meta.date || post.date
  const modified = meta.updatedAt || meta.date || post.date

  return {
    title,
    description,
    keywords: meta.seoKeywords || [],
    authors: (meta.organizationName || meta.author) ? [{ name: meta.organizationName || meta.author }] : undefined,
    category: meta.category,
    creator: meta.organizationName || meta.author,
    publisher: meta.organizationName || meta.author,
    openGraph: {
      type: 'article',
      title,
      description,
      images: image ? [{ url: image, alt: title }] : undefined,
      publishedTime: published,
      modifiedTime: modified,
      authors: meta.organizationName || meta.author ? [meta.organizationName || meta.author] : undefined,
      tags: meta.seoKeywords || [],
    },
    twitter: {
      card: 'summary_large_image',
      title,
      description,
      images: image ? [image] : undefined,
      creator: meta.twitterHandle || undefined,
    },
    other: {
      'reading-time': meta.readingTime || meta.readTimeMinutes ? `${meta.readTimeMinutes} min read` : post.readingTime || '',
      'word-count': meta.wordCount || post.wordCount || '',
      'article:author': meta.organizationName || meta.author || '',
      'article:published_time': published,
      'article:modified_time': modified,
      'article:section': meta.category || '',
      'article:tag': meta.seoKeywords ? meta.seoKeywords.join(', ') : '',
    },
  }
}

export default async function BlogPostPage({ params }: BlogPostPageProps) {
  const { slug } = await params
  const post = getBlogPost(slug)

  if (!post) {
    notFound()
  }

  const PostComponent = post.component
  const meta = (blogMetadata as Record<string, any>)[slug] || {}

  // Create comprehensive JSON-LD schema
  const jsonLd = {
    '@context': 'https://schema.org',
    '@type': 'BlogPosting',
    headline: meta.title || post.title,
    description: meta.description || post.description || '',
    datePublished: meta.createdAt || meta.date || post.date,
    dateModified: meta.updatedAt || meta.date || post.date,
    wordCount: parseInt(meta.wordCount || post.wordCount || '0'),
    timeRequired: `PT${meta.readTimeMinutes || (post.readingTime ? parseInt(post.readingTime) : 5)}M`,
    keywords: (meta.seoKeywords || []).join(', '),
    url: `${process.env.NEXT_PUBLIC_SITE_URL || 'https://your-site.com'}/blog/${slug}`,
    author: {
      '@type': meta.organizationName ? 'Organization' : 'Person',
      name: meta.organizationName || meta.author || 'Your Name',
      ...(meta.organizationName && { url: process.env.NEXT_PUBLIC_SITE_URL || 'https://your-site.com' })
    },
    publisher: {
      '@type': 'Organization',
      name: meta.organizationName || 'Your Site',
      url: process.env.NEXT_PUBLIC_SITE_URL || 'https://your-site.com'
    },
    ...(meta.image && {
      image: {
        '@type': 'ImageObject',
        url: meta.image,
        width: 1200,
        height: 630
      }
    }),
    mainEntityOfPage: {
      '@type': 'WebPage',
      '@id': `${process.env.NEXT_PUBLIC_SITE_URL || 'https://your-site.com'}/blog/${slug}`
    }
  }

  return (
    <div className="container mx-auto px-4 py-8">
      <div className="max-w-4xl mx-auto">
        {/* JSON-LD structured data */}
        <Script id={`json-ld-${post.slug}`} type="application/ld+json" strategy="afterInteractive">
          {JSON.stringify(jsonLd, null, 2)}
        </Script>
        
        {/* Back Button */}
        <Link href="/blog" className="inline-flex items-center gap-2 text-muted-foreground hover:text-foreground bg-neutral-100 dark:bg-neutral-800 px-3 py-2 rounded-md transition-colors duration-200 text-sm mb-4">
            <ArrowLeft className="h-4 w-4" />
            Back to All Blogs
        </Link>
        
        <main>
          <header className="mb-8">
            {meta.image && (
              <div className='flex items-center justify-center w-full my-6'>
                <div className="relative w-full max-w-4xl max-h-[30vh] aspect-[16/9] overflow-hidden rounded-lg">
                  <Image
                    fill
                    style={{ objectFit: 'contain' }}
                    priority
                    src={meta.image}
                    alt={meta.title || post.title || 'Blog post image'}
                  />
                </div>
              </div>
            )}
            <h1 className="text-4xl font-bold mb-4">{meta.title || post.title}</h1>
            {(meta.description || post.description) && (
              <p className="text-muted-foreground mb-4">{meta.description || post.description}</p>
            )}

            <div className="flex flex-wrap items-center gap-4 text-sm text-muted-foreground">
              {(meta.date || post.date) && (
                <time>
                  {new Date(meta.date || post.date).toLocaleDateString()}
                </time>
              )}
              {(meta.readingTime || meta.readTimeMinutes) && (
                <span className="flex items-center gap-1">
                  {meta.readingTime || `${meta.readTimeMinutes} min read`}
                </span>
              )}
              {(meta.wordCount || post.wordCount) && (
                <span className="flex items-center gap-1">
                  {meta.wordCount || post.wordCount} words
                </span>
              )}
              {(meta.author || meta.organizationName) && (
                <span className="flex items-center gap-1">
                  By {meta.author || meta.organizationName}
                </span>
              )}
            </div>
          </header>

          <article className="prose prose-slate dark:prose-invert max-w-none">
            <PostComponent />
          </article>
          
          {/* More Articles Section */}
          {(() => {
            const allPosts = getBlogPosts()
            const otherPosts = allPosts.filter(post => post.slug !== slug)
            const selectedPosts = otherPosts
              .sort(() => Math.random() - 0.5) // Randomize
              .slice(0, 3)

            if (selectedPosts.length === 0) {
              return null
            }

            return (
              <section className="mt-16 pt-8 border-t border-border">
                <div className="flex items-center gap-2 mb-6">
                  <BookOpen className="h-5 w-5 text-muted-foreground" />
                  <h2 className="text-2xl font-bold">More Articles</h2>
                </div>
                
                <div className="grid gap-6 md:grid-cols-2 lg:grid-cols-3">
                  {selectedPosts.map((post: BlogPost) => {
                    const postMetadata = (blogMetadata as Record<string, any>)[post.slug] || {}
                    return (
                      <Link key={post.slug} href={`/blog/${post.slug}`}>
                        <Card className="flex flex-col gap-0 h-full hover:shadow-lg hover:shadow-blue-500/10 hover:border-blue-300 dark:hover:border-blue-600 transition-all duration-300 group cursor-pointer py-0 overflow-hidden">
                          {postMetadata.image && (
                            <div className="relative w-full h-32 overflow-hidden rounded-t-lg">
                              <Image
                                fill
                                style={{ objectFit: 'cover' }}
                                src={postMetadata.image}
                                alt={postMetadata.title || post.title}
                                className="transition-transform duration-300 group-hover:scale-105"
                              />
                            </div>
                          )}
                          <CardHeader className="p-4">
                            <CardTitle className="text-lg line-clamp-2 pb-0.5 font-semibold leading-tight tracking-tight">
                              {post.title}
                            </CardTitle>
                          </CardHeader>
                          <CardContent className="flex-1 p-4 pt-0">
                            {post.description && (
                              <CardDescription className="text-sm line-clamp-2">
                                {post.description}
                              </CardDescription>
                            )}
                          </CardContent>
                          <CardFooter className="pt-0 p-4">
                            <div className="flex items-center justify-between text-sm text-muted-foreground w-full">
                              <div className="flex items-center gap-2 flex-wrap">
                                <div>
                                  {post.date && new Date(post.date).toLocaleDateString()}
                                </div>
                                {postMetadata.readTimeMinutes && (
                                  <div>
                                    {postMetadata.readTimeMinutes} min read
                                  </div>
                                )}
                              </div>
                              <div className="flex items-center gap-1 text-blue-600 dark:text-blue-400 opacity-0 group-hover:opacity-100 transition-opacity duration-200">
                                <span className="text-xs font-medium">Read</span>
                                <ArrowRight className="h-3 w-3" />
                              </div>
                            </div>
                          </CardFooter>
                        </Card>
                      </Link>
                    )
                  })}
                </div>
                
                {otherPosts.length > 3 && (
                  <div className="mt-6 text-center">
                    <Link href="/blog">
                      <div className="inline-flex items-center gap-2 text-muted-foreground hover:text-foreground transition-colors duration-200">
                        <span>View all articles</span>
                        <ArrowRight className="h-4 w-4" />
                      </div>
                    </Link>
                  </div>
                )}
              </section>
            )
          })()}
        </main>
      </div>
    </div>
  )
}
```

## Step 4: Create Blog Utilities
Create a new file at `lib/blog-utils.ts` with the following content:

```tsx
import fs from 'fs'
import path from 'path'
import { blogComponents, blogMetadata } from '../blogs'

export interface BlogPost {
    slug: string
    title: string
    description?: string
    date?: string
    readingTime?: string
    wordCount?: string
    filePath: string
}

const blogsDirectory = path.join(process.cwd(), 'blogs')

export function getBlogPosts(): BlogPost[] {
    if (!fs.existsSync(blogsDirectory)) {
        return []
    }

    const filenames = fs.readdirSync(blogsDirectory)
    const posts: BlogPost[] = []

    for (const filename of filenames) {
        if (filename.endsWith('.tsx')) {
            const slug = filename.replace('.tsx', '')
            const filePath = path.join(blogsDirectory, filename)
            
            // Get metadata from the exported metadata object
            const metadata = (blogMetadata as Record<string, any>)[slug] || {}

            posts.push({
                slug,
                title: metadata.title || slug.replace(/-/g, ' ').replace(/\b\w/g, l => l.toUpperCase()),
                description: metadata.description,
                date: metadata.date,
                readingTime: metadata.readingTime,
                wordCount: metadata.wordCount,
                filePath,
            })
        }
    }

    // Sort by date (newest first) or by title if no date
    return posts.sort((a, b) => {
        if (a.date && b.date) {
            return new Date(b.date).getTime() - new Date(a.date).getTime()
        }
        return a.title.localeCompare(b.title)
    })
}

export function getBlogPost(slug: string) {
    const posts = getBlogPosts()
    const post = posts.find(p => p.slug === slug)

    if (!post) {
        return null
    }

    const component = (blogComponents as Record<string, any>)[slug]
    if (!component) {
        console.error(`Component not found for blog post: ${slug}`)
        return null
    }

    return {
        ...post,
        component,
    }
}
```

## Step 5: Create Blog Registry Script
Create a directory `scripts` and add a file `generate-blog-registry.js` with the following content:

```javascript
const fs = require('fs');
const path = require('path');

function generateBlogRegistry() {
  const blogsDir = path.join(process.cwd(), 'blogs');
  
  if (!fs.existsSync(blogsDir)) {
    console.log('Creating blogs directory...');
    fs.mkdirSync(blogsDir);
  }

  const files = fs.readdirSync(blogsDir)
    .filter(file => file.endsWith('.tsx') && file !== 'index.ts')
    .map(file => file.replace('.tsx', ''));

  // Generate component imports and exports
  const imports = files.map(slug => 
    `import ${slug.replace(/-/g, '')}Component, { metadata as ${slug.replace(/-/g, '')}Metadata } from './${slug}';`
  ).join('\n');

  const componentExports = files.map(slug => 
    `  '${slug}': ${slug.replace(/-/g, '')}Component,`
  ).join('\n');

  const metadataExports = files.map(slug => 
    `  '${slug}': ${slug.replace(/-/g, '')}Metadata,`
  ).join('\n');

  const content = `// Auto-generated file - do not edit manually
// Generated on ${new Date().toISOString()}

${imports}

export const blogComponents = {
${componentExports}
};

export const blogMetadata = {
${metadataExports}
};
`;

  fs.writeFileSync(path.join(blogsDir, 'index.ts'), content);
  console.log(`Generated blog registry with ${files.length} posts`);
}

generateBlogRegistry();
```

## Step 6: Update package.json Scripts
Update your package.json scripts to include the blog registry generation:

```json
{
  "scripts": {
    "dev": "node scripts/generate-blog-registry.js && next dev",
    "build": "node scripts/generate-blog-registry.js && next build",
    "start": "next start",
    "lint": "next lint",
    "blogyak-generate": "node scripts/generate-blog-registry.js"
  }
}
```

## Step 7: Create Blogs Directory
Create a `blogs` directory in your project root where you'll store your blog post files.

## Step 8: Add Custom Typography Styles
Instead of using the Tailwind Typography plugin (which isn't compatible with Tailwind v4), add custom styles to your `app/globals.css` file:

```css
/* Add these custom typography styles to your existing globals.css file */

/* Custom Typography Styles for Blog Posts */
.prose {
  color: var(--foreground);
}

.prose h1 {
  font-size: 2.25rem;
  line-height: 2.5rem;
  font-weight: 800;
  margin-top: 0;
  margin-bottom: 2rem;
}

.prose h2 {
  font-size: 1.875rem;
  line-height: 2.25rem;
  font-weight: 700;
  margin-top: 2rem;
  margin-bottom: 1rem;
}

.prose h3 {
  font-size: 1.5rem;
  line-height: 2rem;
  font-weight: 600;
  margin-top: 1.5rem;
  margin-bottom: 0.75rem;
}

.prose p {
  margin-top: 1.25rem;
  margin-bottom: 1.25rem;
  line-height: 1.75;
}

.prose ul, .prose ol {
  margin-top: 1.25rem;
  margin-bottom: 1.25rem;
  padding-left: 1.625rem;
}

.prose li {
  margin-top: 0.5rem;
  margin-bottom: 0.5rem;
}

.prose blockquote {
  font-weight: 500;
  font-style: italic;
  color: var(--muted-foreground);
  border-left-width: 0.25rem;
  border-left-color: var(--border);
  quotes: "\201C""\201D""\2018""\2019";
  margin-top: 1.6rem;
  margin-bottom: 1.6rem;
  padding-left: 1rem;
}

.prose code {
  color: var(--foreground);
  font-weight: 600;
  font-size: 0.875rem;
  background-color: var(--muted);
  padding: 0.125rem 0.25rem;
  border-radius: 0.25rem;
}

.prose pre {
  background-color: var(--muted);
  color: var(--foreground);
  overflow-x: auto;
  font-weight: 400;
  font-size: 0.875rem;
  line-height: 1.7142857;
  margin-top: 1.7142857rem;
  margin-bottom: 1.7142857rem;
  border-radius: 0.375rem;
  padding: 0.8571429rem 1.1428571rem;
}

.prose pre code {
  background-color: transparent;
  border-width: 0;
  border-radius: 0;
  padding: 0;
  font-weight: inherit;
  color: inherit;
  font-size: inherit;
  font-family: inherit;
  line-height: inherit;
}

.prose strong {
  color: var(--foreground);
  font-weight: 600;
}

.prose a {
  color: var(--primary);
  text-decoration: underline;
  font-weight: 500;
}

.prose a:hover {
  color: var(--primary);
}

/* Dark mode support */
.dark .prose {
  color: var(--foreground);
}

.prose-lg {
  font-size: 1.125rem;
  line-height: 1.7777778;
}

.prose-lg h1 {
  font-size: 2.625rem;
  line-height: 1;
  margin-bottom: 1.2222222rem;
}

.prose-lg h2 {
  font-size: 2.125rem;
  line-height: 1.1764706;
  margin-top: 1.7777778rem;
  margin-bottom: 1.3333333rem;
}

.prose-lg h3 {
  font-size: 1.75rem;
  line-height: 1.4285714;
  margin-top: 1.6666667rem;
  margin-bottom: 0.6666667rem;
}

.prose-lg p {
  margin-top: 1.3333333rem;
  margin-bottom: 1.3333333rem;
}

.prose-lg ul, .prose-lg ol {
  margin-top: 1.3333333rem;
  margin-bottom: 1.3333333rem;
}

.prose-lg li {
  margin-top: 0.6666667rem;
  margin-bottom: 0.6666667rem;
}
```

These custom styles provide better compatibility across all Tailwind versions and give you more control over the styling. If you prefer to use the @tailwindcss/typography plugin with Tailwind v3, you can install it separately.

## Step 9: Add Sitemap Integration
Create a file at `app/sitemap.ts`:

```typescript
import { getBlogPosts } from '@/lib/blog-utils'

export default function sitemap() {
  const posts = getBlogPosts()
  const baseUrl = process.env.NEXT_PUBLIC_SITE_URL || 'http://localhost:3000'

  const blogUrls = posts.map((post) => ({
    url: `${baseUrl}/blog/${post.slug}`,
    lastModified: post.date ? new Date(post.date) : new Date(),
    changeFrequency: 'weekly' as const,
    priority: 0.8,
  }))

  return [
    {
      url: baseUrl,
      lastModified: new Date(),
      changeFrequency: 'yearly' as const,
      priority: 1,
    },
    {
      url: `${baseUrl}/blog`,
      lastModified: new Date(),
      changeFrequency: 'weekly' as const,
      priority: 0.9,
    },
    ...blogUrls,
  ]
}
```

## Step 10: Create Your First Blog Post
Create and add the first blog post to the `blogs` directory. Create a new file `blogs/welcome-to-your-blog.tsx` with the following content:

```tsx
import React from 'react'

export const metadata = {
  title: "Welcome to your blog",
  description: "Learn how to use BlogYak's AI-powered features to effortlessly create engaging blog posts about your projects. This guide walks you through defining your projects, generating content with AI, and customizing your posts for maximum impact.",
  // Dates
  date: "2025-08-12",
  createdAt: "2025-08-12T12:03:32.935Z",
  updatedAt: "2025-08-12T19:46:27.106Z",
  // Authorship
  author: "",
  organizationName: "BlogYak",
  // SEO
  seoKeywords: [],
  slug: "welcome-to-your-blog",
  // Media (always present; empty string if not provided)
  image: "https://www.blogyak.com/thumbnails/welcome-blog.png",
  // Reading stats
  readingTime: "4 min read",
  readTimeMinutes: 4,
  wordCount: "660"
} as const

export default function WelcomeToYourBlog() {
  return (
    <>
      <h1 className="text-3xl font-bold mt-8 mb-6">Welcome to Your Blog!</h1>
      <p className="mb-4">Congratulations on setting up your brand new blog powered by BlogYak! This initial post will walk you through the next steps.</p>
      <h2 className="text-2xl font-bold mt-8 mb-4">How It Works</h2>
      <p className="mb-4">Our blog system is incredibly simple to use:</p>
      <li className="ml-4 mb-1 list-disc">To add a new blog, create a new <code className="bg-gray-100 dark:bg-gray-800 px-2 py-1 rounded text-sm font-mono">.tsx</code> file in the <code className="bg-gray-100 dark:bg-gray-800 px-2 py-1 rounded text-sm font-mono">blogs</code> directory (see this example post for structure). The easiest way to do this is to create and download posts using BlogYak - it comes with metadata and content pre-configured.</li>
      <li className="ml-4 mb-1 list-disc">Run the blog registry script to update the index (pnpm blogyak-generate)</li>
      <li className="ml-4 mb-1 list-disc">To add a thumbnail image to your blog post, put path to your image in the "image" property of metadata in your blog post file.</li>
      <pre className="bg-gray-100 dark:bg-gray-800 p-4 rounded-lg overflow-x-auto my-4 border">
        <code className="language-ts">
{`export const metadata = {
  title: "Welcome to Our Blog",
  ...
  image: "/image.png",
  ...
}`}
        </code>
      </pre>
      <h2 className="text-2xl font-bold mt-8 mb-4">Here's what you should do next:</h2>
      <li className="ml-4 mb-1 list-decimal"><strong className="font-semibold">Create some blog posts:</strong> since you already have your blog system set up, this will be easy. Head over to <a href="https://blogyak.com/dashboard" className="text-blue-600 hover:underline dark:text-blue-400" target="_blank" rel="noopener noreferrer">https://blogyak.com/dashboard</a> to get started. Create a new project, choose from suggested blog posts or describe your own. Generate as many blogs as you like (we recommend at least 10 blogs)</li>
      <li className="ml-4 mb-1 list-decimal"><strong className="font-semibold">Download them:</strong> use "Download All" button to download all the blog posts in your project, move the downloaded blogs posts into your /blogs directory and run "npm blogyak-generate" command</li>
      <li className="ml-4 mb-1 list-decimal"><strong className="font-semibold">Repeat</strong>: Regularly creating new blog posts helps your SEO. Create a couple articles every week.</li>
      <li className="ml-4 mb-1 list-decimal">Join <strong className="font-semibold">Discord</strong> ( <a href="https://discord.gg/zhXvnXw5SY" className="text-blue-600 hover:underline dark:text-blue-400" target="_blank" rel="noopener noreferrer"><strong className="font-semibold">https://discord.gg/zhXvnXw5SY</strong></a> ) and provide feedback so we can make it better. You can also ask for help and any questions.</li>
      <blockquote className="border-l-4 border-gray-300 pl-4 italic text-gray-600 dark:text-gray-400 my-4">To remove this blog post, just delete it from the /blogs directory.</blockquote>
      <h2 className="text-2xl font-bold mt-8 mb-4">What is BlogYak?</h2>
      <p className="mb-4">BlogYak is more than just a blogging platform - it's a powerful tool designed to help you showcase your projects and ideas effectively. Think of BlogYak as your dedicated content assistant, specifically tuned to understand the nuances of your projects.</p>
      <h3 className="text-xl font-semibold mt-6 mb-3">Getting Started: Defining Your Project</h3>
      <p className="mb-4">The foundation of BlogYak's magic lies in understanding your projects. Begin by clearly defining your project by navigating to the "Projects" section. Here, you can enter:</p>
      <li className="ml-4 mb-1 list-disc"><strong className="font-semibold">Project Name:</strong> Give your project a clear and concise title.</li>
      <li className="ml-4 mb-1 list-disc"><strong className="font-semibold">Project Description:</strong> This is where you provide a detailed overview of your project. The more information you provide, the better the AI can tailor the generated content. Describe its purpose, key features, target audience, and any relevant technologies used. For the best results, also provide a step-by-step guide for the main features - this helps BlogYak understand your project from user's perspective.</li>
      <h3 className="text-xl font-semibold mt-6 mb-3">AI Suggestions</h3>
      <p className="mb-4">Sometimes, knowing <em className="italic">what</em> to write about is the biggest challenge. BlogYak provides you with relevant blog post topics based on:</p>
      <li className="ml-4 mb-1 list-disc"><strong className="font-semibold">Existing Blogs:</strong> The AI analyzes your existing blog posts to identify recurring themes and areas where you can delve deeper.</li>
      <li className="ml-4 mb-1 list-disc"><strong className="font-semibold">Project Details:</strong> Your project's name and description.</li>
      <li className="ml-4 mb-1 list-disc"><strong className="font-semibold">Rejected Suggestions:</strong> Topics you've rejected are avoided in future suggestions, ensuring the AI learns from your feedback.</li>
      <p className="mb-4">This feedback loop is crucial. Every "Accept" and "Reject" helps BlogYak learn and become more attuned to your needs, providing increasingly relevant and valuable suggestions over time.</p>
      <h3 className="text-xl font-semibold mt-6 mb-3">AI-Powered Blog Post Generation</h3>
      <p className="mb-4">If you know exactly what you want your blog post to be about, you can simply describe it and AI will generate it for you.</p>
      <li className="ml-4 mb-1 list-decimal"><strong className="font-semibold">Navigate to the</strong> <strong className="font-semibold">Dashboard</strong></li>
      <li className="ml-4 mb-1 list-decimal"><strong className="font-semibold">Select Your Project</strong></li>
      <li className="ml-4 mb-1 list-decimal"><strong className="font-semibold">Let the AI Work its Magic:</strong> Click the "New Blog Post" button, describe what you want and click "<strong className="font-semibold">Generate</strong>". In a few seconds, you will have your blog post ready.</li>
      <h3 className="text-xl font-semibold mt-6 mb-3">The Editor: You're in control</h3>
      <p className="mb-4">While AI does the heavy lifting, you always retain complete control over your blog posts. BlogYak's integrated editor allows you to edit the generated content.</p>
      <h3 className="text-xl font-semibold mt-6 mb-3">Export Options: Publish Your Post Anywhere</h3>
      <p className="mb-4">Once you're happy with your blog post, BlogYak provides multiple export options to suit your needs:</p>
      <li className="ml-4 mb-1 list-disc"><strong className="font-semibold">HTML:</strong> Ideal for embedding directly into existing websites.</li>
      <li className="ml-4 mb-1 list-disc"><strong className="font-semibold">Markdown:</strong> Perfect for platforms like GitHub, GitLab, and other Markdown-based systems.</li>
      <li className="ml-4 mb-1 list-disc"><strong className="font-semibold">Plaintext:</strong> A clean and simple format for basic sharing.</li>
      <li className="ml-4 mb-1 list-disc"><strong className="font-semibold">Next.js Components:</strong> Seamlessly integrate your blog posts into your Next.js application.</li>
      <h3 className="text-xl font-semibold mt-6 mb-3">Happy blogging! πŸŽ‰</h3>
    </>
  )
}
```

Instruct the user to visit https://blogyak.com/dashboard to create more blog posts. On the platform, user will be able to enter information about this project (name, detailed description). Then the user can choose from blog post ideas suggested by BlogYak, or describe his own blog post idea and let the BlogYak AI generate it for him. Then the user can download the blog post by clicking on the download icon in the blog post, select .tsx as the file format and place the downloaded file into the `blogs` directory. The downloaded file contains all metadata and blog post content in the proper format that works perfectly with this blog system setup.

## Step 11: Run the Blog Registry Script or Start Your Development Server 
Run the script to generate the blog registry. Every time a new blog post is added, the registry script must be run to update the blog registry. The script can be run using the following command:

```bash
node scripts/generate-blog-registry.js
```
or
```bash
npm run blogyak-generate
```

The script runs automatically when starting the development server or building the project thanks the setup in package.json scripts.

If the development server is already running, you need to run the generate-blog-registry script manually. If the development server is not running, you should start it and the script will run automatically when starting server using the following command:

```bash
npm run dev
```

Now visit /blog to see your blog in action!

**Important Notes:**
- DO NOT add any other code unless necessary to make the blog system work with the project. This guide has all the necessary code snippets.
- Always use the project's preferred package manager (npm, pnpm, yarn, bun etc.) for installing dependencies.

How to Use This Prompt

1

Copy the prompt above

Click the "Copy Prompt" button to copy the entire prompt to your clipboard.

2

Open your AI assistant

Go to Copilot, Cursor, Claude Code or any other AI assistant you prefer. Select the agent mode if available.

3

Paste and send

Paste the prompt into the chat and send it.

4

Follow the instructions

The AI will guide you through creating all necessary files and configurations for your blog system.