SSG Mini Project — Build a Complete Static Site from Scratch
In this tutorial, you will learn about SSG Mini Project. We cover key concepts, practical examples, and best practices to help you master this topic.
Build a complete SSG-powered blog from scratch combining Next.js, markdown content, image optimization, search, and deployment to Netlify.
What You'll Learn
By the end of this project, you'll build a production-ready static site that demonstrates all the SSG concepts covered in this course: content management, data fetching, image optimization, search, analytics, and deployment.
Why It Matters
Theory without practice doesn't stick. This project brings together every SSG technique you've learned, giving you a real-world portfolio piece that showcases your static site generation skills.
Real-World Use
A developer builds their personal blog as an SSG project. It has markdown-based posts, optimized images, client-side search, privacy-friendly analytics, and auto-deploys on git push. This exact architecture powers thousands of production blogs.
Project Architecture
graph TD
A[SSG Blog Project] --> B[Next.js SSG]
B --> C[getStaticProps
+ getStaticPaths]
C --> D[Markdown content
in /content]
D --> E[Image optimization
Next/Image + Sharp]
B --> F[Search
Lunr.js client-side]
B --> G[Analytics
Plausible]
B --> H[SEO
meta tags + sitemap]
B --> I[Deployment
Netlify + CI/CD]
E --> J[Build output
static HTML]
F --> J
G --> J
H --> J
I --> J
J --> K[Global CDN]
style A fill:#4a90d9,color:#fff
style B fill:#e67e22,color:#fff
style J fill:#27ae60,color:#fff
Project Setup
// next.config.js — SSG blog configuration
const withMDX = require('@next/mdx')({
extension: /\.mdx?$/,
});
module.exports = withMDX({
output: 'export',
pageExtensions: ['js', 'jsx', 'ts', 'tsx', 'md', 'mdx'],
images: {
unoptimized: true,
},
});
// scripts/generate-content.js — Content generation script
const fs = require('fs');
const path = require('path');
const postsDir = './content/posts';
const publicDir = './public';
// Ensure directories exist
[postsDir, publicDir].forEach(dir => {
if (!fs.existsSync(dir)) {
fs.mkdirSync(dir, { recursive: true });
}
});
// Generate sample posts
const posts = [
{
slug: 'getting-started-with-ssg',
title: 'Getting Started with Static Site Generation',
date: '2026-06-28',
excerpt: 'Learn the fundamentals of SSG and why it matters for modern web development.',
tags: ['ssg', 'tutorial'],
content: '## What is SSG?\n\nStatic Site Generation pre-builds HTML pages at compile time...\n\n## Benefits\n- Lightning fast load times\n- No server required\n- Secure by default\n\n## Code Example\n```javascript\nconst site = buildStaticSite();\n```'
},
{
slug: 'image-optimization-guide',
title: 'Complete Image Optimization Guide for Static Sites',
date: '2026-06-27',
excerpt: 'Master image optimization techniques to keep your static site fast and visually appealing.',
tags: ['images', 'performance'],
content: '## Why Image Optimization Matters\n\nImages account for 50-70% of page weight...\n\n## Best Practices\n- Use WebP format\n- Generate responsive sizes\n- Lazy load below-fold images'
},
{
slug: 'deploying-static-sites',
title: 'Deploying Static Sites to Production',
date: '2026-06-26',
excerpt: 'A step-by-step guide to deploying SSG sites on Netlify, Vercel, and Cloudflare Pages.',
tags: ['deployment', 'devops'],
content: '## Deployment Options\n\n### Netlify\nAutomatic deploys from Git...\n\n### Vercel\nOptimized for Next.js...\n\n### Cloudflare Pages\nGlobal edge network...'
}
];
Core Implementation
// pages/index.js — Blog homepage
export default function Home({ posts, buildTime }) {
return (
<div className="container">
<header>
<h1>SSG Blog Project</h1>
<p>Built with Next.js SSG</p>
<input
type="search"
id="search"
placeholder="Search posts..."
className="search-input"
/>
</header>
<div className="posts-grid">
{posts.map(post => (
<article key={post.slug} className="post-card">
<h2>
<a href={`/posts/${post.slug}`}>{post.title}</a>
</h2>
<time>{new Date(post.date).toLocaleDateString()}</time>
<p>{post.excerpt}</p>
<div className="tags">
{post.tags.map(tag => (
<span key={tag} className="tag">{tag}</span>
))}
</div>
</article>
))}
</div>
<footer>
<p>Built at: {new Date(buildTime).toISOString()}</p>
<p>{posts.length} posts generated</p>
</footer>
</div>
);
}
export async function getStaticProps() {
const postsDir = path.join(process.cwd(), 'content', 'posts');
const filenames = fs.readdirSync(postsDir);
const posts = filenames
.filter(f => f.endsWith('.md'))
.map(f => {
const filePath = path.join(postsDir, f);
const content = fs.readFileSync(filePath, 'utf8');
const { data, content: body } = matter(content);
return {
slug: f.replace('.md', ''),
title: data.title,
date: data.date.toISOString(),
excerpt: data.excerpt,
tags: data.tags || [],
};
})
.sort((a, b) => new Date(b.date) - new Date(a.date));
return {
props: {
posts,
buildTime: Date.now(),
},
};
}
// pages/posts/[slug].js — Individual post page
export default function Post({ post, buildTime }) {
return (
<article className="post">
<header>
<a href="/" className="back-link">← Back to Home</a>
<h1>{post.title}</h1>
<div className="meta">
<time>{new Date(post.date).toLocaleDateString()}</time>
<div className="tags">
{post.tags.map(tag => (
<span key={tag} className="tag">{tag}</span>
))}
</div>
</div>
</header>
<div className="content">
{post.content}
</div>
<nav className="post-nav">
{post.prev && (
<a href={`/posts/${post.prev.slug}`} className="prev">
← {post.prev.title}
</a>
)}
{post.next && (
<a href={`/posts/${post.next.slug}`} className="next">
{post.next.title} →
</a>
)}
</nav>
</article>
);
}
export async function getStaticPaths() {
const postsDir = path.join(process.cwd(), 'content', 'posts');
const filenames = fs.readdirSync(postsDir);
const paths = filenames
.filter(f => f.endsWith('.md'))
.map(f => ({
params: { slug: f.replace('.md', '') },
}));
return { paths, fallback: false };
}
Deployment Script
# .github/workflows/deploy.yml — GitHub Actions deployment
name: Deploy SSG Site
on:
push:
branches: [main]
workflow_dispatch:
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 18
cache: 'npm'
- run: npm ci
- name: Build static site
run: npm run build
- name: Deploy to Netlify
uses: nwtgck/actions-netlify@v2
with:
publish-dir: './out'
production-branch: main
github-token: ${{ secrets.GITHUB_TOKEN }}
deploy-message: 'Deploy from GitHub Actions'
env:
NETLIFY_AUTH_TOKEN: ${{ secrets.NETLIFY_AUTH_TOKEN }}
NETLIFY_SITE_ID: ${{ secrets.NETLIFY_SITE_ID }}
Common Mistakes
- Skipping error handling in data fetching. Build-time errors crash the entire build. Wrap API calls and file reads in try-catch blocks.
- Not optimizing the LCP image. The hero image on each page should load eagerly with priority. Other images should lazy load.
- Forgetting to add meta tags for SEO. Each page needs unique title, description, and Open Graph tags. Use Next.js Head component.
- Not testing the build before deployment. Always run
npm run buildlocally. Build failures waste CI/CD pipeline time. - Ignoring mobile responsiveness. Test on actual mobile devices, not just browser dev tools. Use CSS media queries for all breakpoints.
Practice Questions
- What is the recommended project structure for an SSG blog?
- How do you implement post pagination on the homepage?
- How do you create prev/next navigation between posts?
- What caching Strategy should you use for static assets vs HTML?
- How do you set up automatic deployment from a git Repository?
Challenge: Extend the SSG blog project with: category and tag pages, RSS feed generation, sitemap.xml, a custom 404 page, dark mode toggle, and search keyboard shortcut (Ctrl+K).
FAQ
Mini Project
Build and deploy your own SSG blog: create 5+ posts in markdown, implement homepage listing, individual post pages, tag/category pages, image optimization, client-side search, privacy-friendly analytics, and auto-deploy via GitHub Actions to Netlify.
What's Next
Congratulations on completing the SSG course! You've built a complete static site. Now explore the next frontend topic: Incremental Static Regeneration to add automatic content updates without full rebuilds.
Built by the developers of DodaTech
Doda Browser, DodaZIP & Durga Antivirus Pro