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
- CMS plugins - building tools for non-technical users is its own skill
- B2B design - professional doesn't mean boring
- Performance - every millisecond matters
- 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
Mind-ID Portal Recruitment - Enterprise Recruitment Platform
Spearheaded the frontend of a multi-subsidiary recruitment portal using Next.js 14 and Shadcn UI.
Music Distribution Platform - Licensing & Digital Contracts
Built a music distribution platform with payment gateway, digital signatures, and 20GB file uploads.
PAMAFix SAP - Transforming Monolith to Microservices
Revamping a monolithic ERP into microservices architecture - OTC module with dynamic forms, error handling, and .NET integration.