Building a modern multilingual platform post image

Building a modern multilingual platform

(Updated Feb 10, 2025)

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:

  1. Automatic Cache Invalidation: Keeps data fresh without manual intervention
  2. Real-time Updates: Enables optimistic UI updates
  3. Request Deduplication: Prevents redundant API calls
  4. Built-in Error Handling: Simplifies error states management
  5. 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

  1. Type Safety is Crucial: TypeScript caught numerous potential issues during development
  2. SWR is Powerful: The combination of caching and real-time updates improved UX significantly
  3. Performance Matters: Early optimization decisions paid off in the long run
  4. Security First: Implementing security at every layer prevented potential vulnerabilities
  5. 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!

Subscribe

Get an email when i write new posts. Learn animation techniques, CSS, design systems and more