Last Week
A simple web frontend is made using Hugo and JAMStack. It is connected to the Supabase backend. Also began investigation of monetization infrastructure in a KMP project, which will be next week’s focus.
What does it mean in English?
Think of JAMStack as a modern recipe for building websites. The name “JAM” is an acronym that stands for:
JavaScript: The ingredient that makes websites interactive APIs: Like digital messengers that fetch information from other services Markup: The structure and content of your website
In simpler terms, JAMStack is like building a website as if you were assembling furniture from IKEA - the pieces are pre-made, and you just put them together.
Nerdy Details
The key characteristic of JAMstack is the pre-rendering of pages during build time rather than serving them dynamically at request time. This approach offers benefits like improved performance, better security, lower cost of scaling, and an improved developer experience.
We will use Hugo as the site builder and Supabase as the backend service.
First, you’ll need to add the Supabase JavaScript client to your Hugo site. Create a JavaScript file in your Hugo assets directory:
// assets/js/supabase.js
import { createClient } from '@supabase/supabase-js';
// Initialize the Supabase client
const supabaseUrl = 'https://your-project-url.supabase.co';
const supabaseKey = 'your-anon-key'; // Public anon key, safe to use in browser
export const supabase = createClient(supabaseUrl, supabaseKey);
// Example function to fetch data
export async function fetchPosts() {
const { data, error } = await supabase
.from('posts')
.select('*')
.order('created_at', { ascending: false });
if (error) {
console.error('Error fetching posts:', error);
return [];
}
return data;
}
For client-side rendering of dynamic content, create a partial template in Hugo:
<!-- layouts/partials/posts-list.html -->
<div id="posts-container">
<!-- Posts will be loaded here via JavaScript -->
<p>Loading posts...</p>
</div>
<script>
document.addEventListener('DOMContentLoaded', async () => {
try {
// Import the supabase client and functions
const { fetchPosts } = await import('/js/supabase.js');
const posts = await fetchPosts();
const postsContainer = document.getElementById('posts-container');
if (posts.length === 0) {
postsContainer.innerHTML = '<p>No posts found.</p>';
return;
}
const postsHtml = posts.map(post => `
<article class="post">
<h2>${post.title}</h2>
<p>${post.excerpt}</p>
<a href="/posts/${post.slug}">Read more</a>
</article>
`).join('');
postsContainer.innerHTML = postsHtml;
} catch (error) {
console.error('Error:', error);
document.getElementById('posts-container').innerHTML =
'<p>Failed to load posts. Please try again later.</p>';
}
});
</script>
For content that doesn’t change frequently, you can fetch data during build time using Hugo’s data templates:
// data/fetch-posts.js (Node.js script to run before Hugo build)
const { createClient } = require('@supabase/supabase-js');
const fs = require('fs');
const path = require('path');
async function fetchPosts() {
const supabase = createClient(
'https://your-project-url.supabase.co',
'your-service-role-key' // Use service role key for server-side operations
);
const { data, error } = await supabase
.from('posts')
.select('*')
.order('created_at', { ascending: false });
if (error) {
console.error('Error fetching posts:', error);
return;
}
// Save the data as a JSON file that Hugo can use
const dataDir = path.join(__dirname, '../data');
if (!fs.existsSync(dataDir)) {
fs.mkdirSync(dataDir, { recursive: true });
}
fs.writeFileSync(
path.join(dataDir, 'posts.json'),
JSON.stringify(data, null, 2)
);
console.log(`Successfully fetched ${data.length} posts`);
}
fetchPosts().catch(console.error);
Then in your Hugo template:
<!-- layouts/index.html -->
<div class="posts">
{{ range site.Data.posts }}
<article class="post">
<h2>{{ .title }}</h2>
<p>{{ .excerpt }}</p>
<a href="/posts/{{ .slug }}">Read more</a>
</article>
{{ end }}
</div>
Since Hugo generates static pages, you’ll need to implement client-side authentication checks:
// assets/js/protected-route.js
import { getCurrentUser } from './auth.js';
async function checkAuth() {
const { data: { user } } = await getCurrentUser();
if (!user) {
// Redirect to login page if not authenticated
window.location.href = '/login?redirect=' + encodeURIComponent(window.location.pathname);
}
}
// Run the check when the script loads
checkAuth();
Then include this script in the head of your protected templates:
<!-- layouts/members/single.html -->
{{ define "head" }}
<script type="module" src="/js/protected-route.js"></script>
{{ end }}
{{ define "main" }}
<article class="protected-content">
<h1>{{ .Title }}</h1>
{{ .Content }}
</article>
{{ end }}
Then build the hugo site and voila.
Next Week
Explore the subscription model and start thinking about marketing.