
Introduction
Recently, I completed a significant project for Helge Haukeland, a renowned Norwegian taxidermist and filmmaker. The project required building a sophisticated multilingual platform that could handle everything from content management to e-commerce. In this post, I’ll dive deep into the technical decisions, challenges, and solutions implemented.
Why Next.js 13 with TypeScript?
The choice of Next.js 13 as our foundation wasn’t just about following trends. Several key factors influenced this decision:
- Server-side Rendering: Critical for SEO and initial page load performance
- API Routes: Enabled backend functionality without additional server setup
- Image Optimization: Built-in image optimization saved significant development time
- TypeScript Integration: Provided type safety across the entire codebase
Data Management with SWR
One of the most interesting technical decisions was using SWR for data fetching. Here’s why:
// Example of SWR implementation for watermark settings
export function useWatermarkSettings() {
const { data, error, isLoading } = useSWR("/api/settings", async (url) => {
const res = await fetch(url);
const data = await res.json();
return data.watermark;
});
return {
settings: data,
isLoading,
isError: error,
};
}
SWR provided several key benefits:
- Automatic Cache Invalidation: Keeps data fresh without manual intervention
- Real-time Updates: Enables optimistic UI updates
- Request Deduplication: Prevents redundant API calls
- Built-in Error Handling: Simplifies error states management
- Automatic Revalidation: Keeps data in sync across tabs
Database architecture with Prisma
The decision to use Prisma ORM with MySQL was driven by several factors.
- Type-safe database queries
- Automatic migration management
- Complex relationship handling
- Efficient connection pooling
Custom editor extensions
The project features three sophisticated custom editor extensions:
Gallery Extension
const handleMediaLibraryInsert = async (selectedImages) => {
const transformedImages = await Promise.all(
selectedImages.map(async (image) => {
const optimizedUrl = await transformCloudinaryUrl({
url: image.url,
usage: "gallery"
});
return { ...image, url: optimizedUrl };
})
);
// Further processing...
};
Related Posts Extension
const loadPosts = async () => {
const response = await fetch(`/api/posts?type=${postType}&locale=${locale}`);
const data = await response.json();
setPosts(data.posts);
};
Slider Extension
interface SlideData {
id: string;
imageUrl: string;
content: string;
title?: string;
position: "left" | "right";
}
Authentication and security
Security was implemented through multiple layers:
// Example of auth configuration
export const authOptions: NextAuthOptions = {
adapter: PrismaAdapter(db),
session: {
strategy: "jwt",
},
callbacks: {
async jwt({ token, user }) {
// Custom JWT handling
return token;
},
async session({ token, session }) {
// Session customization
return session;
}
}
};
Media Management
The media system includes sophisticated handling:
export async function transformCloudinaryUrl({ url, usage }: TransformImageOptions) { const settingsResponse = await fetch("/api/settings"); const settingsData = await settingsResponse.json(); const { watermark, image } = settingsData; // Dynamic transformation based on usage and settings let transformations: string[] = []; if (image?.enabled && image[usage]?.enabled) { // Apply quality settings } if (watermark?.enabled && watermark?.applyTo?.[usage]) { // Apply watermark } return transformedUrl; }
Performance Optimizations
Several key optimizations were implemented:
1. Image Optimization
<Image
src={image}
alt={title}
fill
priority
className="object-cover"
style={{ objectPosition: "center 40%" }}
/>
2. Code Splitting
const DynamicComponent = dynamic(() => import('./HeavyComponent'), {
loading: () => <LoadingSpinner />
});
3. API Response Caching
export async function getStaticProps() {
// Implementation with ISR
return {
props: { data },
revalidate: 60
};
}
Lessons Learned
- Type Safety is Crucial: TypeScript caught numerous potential issues during development
- SWR is Powerful: The combination of caching and real-time updates improved UX significantly
- Performance Matters: Early optimization decisions paid off in the long run
- Security First: Implementing security at every layer prevented potential vulnerabilities
- Modular Design: Breaking down features into modular components improved maintainability
Conclusion
Building the platform for Helge Haukeland was a journey in balancing modern web technologies with practical business requirements. The combination of Next.js, TypeScript, SWR, and custom solutions created a robust, maintainable, and performant platform.
The project demonstrates how careful technology choices and architectural decisions can create a system that’s not just technically sophisticated but also provides real business value.
Feel free to reach out if you have questions about any of the technical implementations discussed in this post!