EG
Resume

new project just dropped. 🚀

The Brief

Resona Perdania needed their corporate website updated:

  • Look more modern, minimalistic
  • Easy navigation
  • Maintain B2B professional identity
  • Keep green, orange, white branding
  • Add subtle Japanese accents (their parent company is Japanese)
  • Simplify menu structure
  • Keep all existing content

What I Built

Corporate Website (Next.js + Strapi)

Frontend - Next.js 14 with App Router:

  • Server Components for optimal performance
  • App Router for clean routing
  • Shadcn UI + Tailwind for styling
  • Image optimization with Next/Image
  • CDN caching strategies

Backend - Custom Strapi CMS:

They were using a paid CMS before. I said: "let me build custom plugins to replace those features." saved them monthly subscription costs 💰

Custom CMS Plugins Built

1. Audit Log Plugin

Track every content change:

// Plugin: audit-log/server/content-types/audit-log/schema.ts
module.exports = {
  kind: 'collectionType',
  collectionName: 'audit_logs',
  attributes: {
    action: { type: 'string' },
    user: { type: 'string' },
    contentType: { type: 'string' },
    entryId: { type: 'integer' },
    changes: { type: 'json' },
    timestamp: { type: 'datetime' }
  }
};

2. Draft Preview Plugin

Preview content before publishing:

// Preview button in editor
const DraftPreview = ({ contentType, entryId }) => {
  const previewUrl = `${FRONTEND_URL}/api/preview?secret=${PREVIEW_SECRET}&type=${contentType}&slug=${entryId}`;

  return <Button onClick={() => window.open(previewUrl)}>Preview Draft</Button>;
};

3. Import/Export Plugin

Bulk content management:

// Export functionality
const exportContent = async (contentType: string, format: 'json' | 'csv') => {
  const data = await strapi.entityService.findMany(contentType);
  const blob = new Blob([JSON.stringify(data)], { type: 'application/json' });
  saveAs(blob, `${contentType}-export.${format}`);
};

// Import with validation
const importContent = async (file: File) => {
  const data = await parseFile(file);
  const validated = data.map(schema.parse);
  await strapi.entityService.createMany(contentType, validated);
};

4. Advanced Search Plugin

// Search across all content types
const searchContent = async (query: string) => {
  const results = await strapi.entityService.search(query, {
    pageSize: 20,
    filters: {
      or: [
        { title: { $contains: query } },
        { description: { $contains: query } },
        { content: { $contains: query } }
      ]
    }
  });
  return results;
};

Design System

Japanese-inspired B2B aesthetic:

  • Clean typography (Inter + Noto Sans JP)
  • Generous whitespace
  • Subtle animations (no flashy stuff)
  • Green/Orange/White from their brand guidelines
  • Minimalist iconography
  • Card-based layouts
// Design tokens
const tokens = {
  colors: {
    primary: '#2D5A27', // forest green
    secondary: '#F97316', // vibrant orange
    neutral: '#FFFFFF',
    text: '#1F2937',
    textMuted: '#6B7280'
  },
  typography: {
    heading: 'Inter, sans-serif',
    body: 'Inter, sans-serif',
    japanese: 'Noto Sans JP, sans-serif'
  },
  spacing: {
    section: '80px',
    component: '24px',
    element: '16px'
  }
};

Performance Optimization

  • Static generation for public pages
  • ISR for frequently updated content
  • Image optimization (WebP, lazy loading)
  • CDN caching (Cloudflare)
  • Bundle size analysis
// next.config.js
module.exports = {
  images: {
    formats: ['image/avif', 'image/webp'],
    deviceSizes: [640, 750, 828, 1080, 1200, 1920]
  },
  async headers() {
    return [{
      source: '/:path*',
      headers: [
        { key: 'Cache-Control', value: 'public, max-age=31536000, immutable' }
      ]
    }];
  }
};

Content Workflow

Draft → Review → Preview → Publish

// Draft-to-publish workflow
const publishContent = async (entryId: number) => {
  // 1. Validate all required fields
  const entry = await strapi.entityService.findOne('article', entryId);

  if (!entry.title || !entry.content) {
    throw new Error('Missing required fields');
  }

  // 2. Generate SEO metadata
  const seo = {
    metaTitle: entry.title,
    metaDescription: entry.content.substring(0, 160),
    slug: generateSlug(entry.title)
  };

  // 3. Update with published status
  await strapi.entityService.update('article', entryId, {
    data: { ...seo, publishedAt: new Date() }
  });
};

Results

  • ✅ Custom CMS plugins replacing paid SaaS
  • ✅ Page load time reduced by 60%
  • ✅ Modern, professional B2B presence
  • ✅ Content team can manage everything without developer
  • ✅ Japanese-inspired aesthetic that feels premium
  • ✅ SEO optimized with proper meta tags

What I Learned

  1. CMS plugins - building tools for non-technical users is its own skill
  2. B2B design - professional doesn't mean boring
  3. Performance - every millisecond matters
  4. Content workflows - publishers need proper approval processes

corporate websites aren't exciting but they're important. and when you can replace paid tools with custom solutions? that feels good.

Related Articles