When building content-driven products (courses, documentation, blogs), several common challenges usually come up:
- Where should content live? Database or files?
- How do we balance SEO and access control?
- Do content updates require redeployment?
- How do we avoid exposing the data source to the client?
In this article, we share a practical approach we use in real projects:
Using GitHub + Next.js + MDX to support SSG previews with authenticated full content loading
This setup helps teams keep content workflows flexible without giving up SEO, performance, or access control.
Core Idea
The overall architecture can be broken down into four layers:
- GitHub: content storage (private repository)
- Server layer: content fetching, caching, and access control
- SSG pages: pre-rendered preview content
- Client: loads full content after authentication
The key principle is:
GitHub acts only as a data source and is never exposed directly to the client.
1. Content Source: GitHub API
All content is stored in a private GitHub repository (in MDX format) and fetched via the GitHub API, instead of being downloaded at build time.
This approach provides several benefits:
- Content updates do not require redeployment
- Full Git workflow support (PRs, version history)
- Content repository can be maintained independently
At the lowest level, a loadMdx function is responsible for fetching raw MDX content from GitHub.
2. Unified Content Layer
Instead of letting pages, APIs, and caching logic access GitHub directly, we introduce a unified content service layer.
For example:
getLessonContent
This layer is responsible for:
- Combining metadata and content
- Providing a single entry point for content access
- Handling caching logic
Both:
- SSG page rendering
- Content API responses
depend on this layer.
3. SSG Rendering: Preview Content Only
Pages are statically generated (SSG) during build time, but only render partial content.
This serves two purposes:
- Initial page load performance
- SEO indexing
This design allows us to keep the benefits of SSG while still supporting gated content.
4. Loading Full Content After Login
The full content is not included in the SSG output.
After authentication, the client requests an internal API to retrieve the full content:
- The server validates authentication
- Calls the content service (usually hitting cache)
- Returns the complete MDX
Importantly:
The client never directly calls the GitHub API
This ensures:
- No exposure of access tokens
- Proper access control on the server side
- Decoupling from the external data source
5. Caching and Content Updates
To avoid hitting GitHub on every request, content is cached at the server layer.
We use Next.js caching features:
cachefor memoizationtagfor grouping content
When content is updated, a Webhook triggers cache invalidation:
- GitHub repository receives a push
- Webhook is triggered
- Calls
/content-revalidate - The server executes:
revalidateTagto refresh cached contentrevalidatePathto refresh affected pages
This allows content updates without redeployment.
6. Limitation: New Pages
One important limitation:
revalidatecannot generate new SSG pages
This means:
- Existing pages → can be updated
- New routes/pages → will not be statically generated automatically
If new content needs to be included in SSG, a redeployment is still required.
Recommended Strategy
In practice, we combine both approaches:
- Content updates → Webhook +
revalidate - New routes → trigger deployment
This balances flexibility and stability.
Summary
This approach is essentially a combination of:
SSG + Content API + Caching + Webhook
It addresses several key challenges:
- Content updates without redeployment
- Preserving SEO and performance via SSG
- Supporting authenticated access to full content
- Avoiding direct exposure of the data source to the client
If you're interested in the implementation details (such as content loading, caching strategies, and Webhook handling), you can explore the project directly:
https://github.com/raytonx-labs/raytonx-learn