perf: optimize portfolio runtime delivery
Some checks failed
Test and deploy / Verify production build (push) Has been cancelled
Test and deploy / Deploy production (push) Has been cancelled

Replace the blocking asset loader with progressive rendering, defer expensive media and Three.js work, and reduce hydration and animation costs. Move deployment from a static Nginx export to the standalone Next.js runtime with optimized dynamic images.
This commit is contained in:
2026-09-09 12:43:37 +03:30
parent 9143ba8504
commit f34bea05cd
63 changed files with 1296 additions and 1048 deletions

View File

@@ -1,11 +1,12 @@
import type { Locale, Localized } from '@/i18n/content';
import { portfolioImage, type PortfolioImage } from '@/types/media';
export interface EducationItem {
degree: string;
institution: string;
date: string;
description: string;
logo: string;
logo: PortfolioImage;
}
type EducationCopy = Omit<EducationItem, 'logo'>;
@@ -29,7 +30,7 @@ const educationContent: Localized<EducationCopy> = {
export const getEducation = (locale: Locale): EducationItem => ({
...educationContent[locale],
logo: '/images/logo-guilan-crest.png',
logo: portfolioImage('/images/logo-guilan-crest.png', 204, 144),
});
export const education = getEducation('en');

View File

@@ -1,4 +1,5 @@
import type { Locale, Localized } from '@/i18n/content';
import { portfolioImage, type PortfolioImage } from '@/types/media';
export interface ExperienceItem {
id: string;
@@ -6,7 +7,7 @@ export interface ExperienceItem {
company: string;
date: string;
location: string;
logo: string;
logo: PortfolioImage;
isCurrent?: boolean;
summary: string;
bullets: string[];
@@ -24,7 +25,7 @@ interface ExperienceDefinition extends Omit<ExperienceItem, keyof ExperienceCopy
const experienceDefinitions: ExperienceDefinition[] = [
{
id: 'quera',
logo: '/images/logo-quera.png',
logo: portfolioImage('/images/logo-quera.png', 72, 72),
isCurrent: true,
content: {
en: {
@@ -59,7 +60,7 @@ const experienceDefinitions: ExperienceDefinition[] = [
},
{
id: 'fiotrix',
logo: '/images/logo-fiotrix.png',
logo: portfolioImage('/images/logo-fiotrix.png', 72, 72),
isCurrent: false,
content: {
en: {
@@ -100,7 +101,7 @@ const experienceDefinitions: ExperienceDefinition[] = [
},
{
id: 'baduno',
logo: '/images/logo-baduno.png',
logo: portfolioImage('/images/logo-baduno.png', 72, 72),
isCurrent: false,
content: {
en: {
@@ -139,7 +140,7 @@ const experienceDefinitions: ExperienceDefinition[] = [
},
{
id: 'sharif-ta',
logo: '/images/logo-sharif.png',
logo: portfolioImage('/images/logo-sharif.png', 72, 72),
isCurrent: false,
content: {
en: {
@@ -170,7 +171,7 @@ const experienceDefinitions: ExperienceDefinition[] = [
},
{
id: 'guilan-assoc',
logo: '/images/logo-guilan-assoc.png',
logo: portfolioImage('/images/logo-guilan-assoc.png', 72, 72),
isCurrent: false,
content: {
en: {
@@ -201,7 +202,7 @@ const experienceDefinitions: ExperienceDefinition[] = [
},
{
id: 'guilan-ta-head',
logo: '/images/logo-guilan-ta.png',
logo: portfolioImage('/images/logo-guilan-ta.png', 72, 72),
isCurrent: false,
content: {
en: {

View File

@@ -1,4 +1,5 @@
import type { Locale, Localized } from '@/i18n/content';
import { portfolioImage, type PortfolioImage } from '@/types/media';
export interface MilestoneItem {
id: string;
@@ -6,7 +7,7 @@ export interface MilestoneItem {
issuer: string;
date: string;
description: string;
image: string;
image: PortfolioImage;
link?: string;
}
@@ -22,7 +23,7 @@ interface MilestoneDefinition extends Omit<MilestoneItem, keyof MilestoneCopy> {
const milestoneDefinitions: MilestoneDefinition[] = [
{
id: 'hackathon',
image: '/images/cert-hackathon.png',
image: portfolioImage('/images/cert-hackathon.png', 2159, 1510),
link: 'https://starcoach.ir/certificate/STC20263139',
content: {
en: {
@@ -43,7 +44,7 @@ const milestoneDefinitions: MilestoneDefinition[] = [
},
{
id: 'icpc-2025',
image: '/images/cert-icpc-2025.png',
image: portfolioImage('/images/cert-icpc-2025.png', 1082, 768),
link: 'https://www.linkedin.com/in/amiirkhl/overlay/Certifications/1331555202/treasury/?profileId=ACoAAEJsAwMBCv3L9dYcEXWam-l3pLOJ3UspX7w',
content: {
en: {
@@ -64,7 +65,7 @@ const milestoneDefinitions: MilestoneDefinition[] = [
},
{
id: 'pm-bootcamp',
image: '/images/cert-bootcamp.png',
image: portfolioImage('/images/cert-bootcamp.png', 3627, 2599),
link: 'https://www.linkedin.com/in/amiirkhl/overlay/Certifications/812043696/treasury/?profileId=ACoAAEJsAwMBCv3L9dYcEXWam-l3pLOJ3UspX7w',
content: {
en: {
@@ -85,7 +86,7 @@ const milestoneDefinitions: MilestoneDefinition[] = [
},
{
id: 'icpc-2024',
image: '/images/cert-icpc-2024.png',
image: portfolioImage('/images/cert-icpc-2024.png', 1078, 768),
content: {
en: {
title: 'ICPC 2024 - Online Participant',
@@ -105,7 +106,7 @@ const milestoneDefinitions: MilestoneDefinition[] = [
},
{
id: 'django-bootcamp',
image: '/images/cert-django.png',
image: portfolioImage('/images/cert-django.png', 3508, 2480),
link: 'https://quera.org/certificate/F1mYkTZR/',
content: {
en: {

View File

@@ -1,4 +1,5 @@
import type { Locale, Localized } from '@/i18n/content';
import { portfolioImage, type PortfolioImage } from '@/types/media';
export interface Project {
id: string;
@@ -7,7 +8,7 @@ export interface Project {
date: string;
description: string;
tags: string[];
image: string;
image: PortfolioImage;
}
type ProjectCopy = Pick<Project, 'title' | 'date' | 'description'>;
@@ -21,7 +22,7 @@ const projectDefinitions: ProjectDefinition[] = [
id: 'qlockify',
link: 'https://qlockfiy.ir',
tags: ['Django', 'React', 'Docker', 'UI/UX Design'],
image: '/images/project-qlockify.png',
image: portfolioImage('/images/project-qlockify.png', 359, 200),
content: {
en: {
title: 'Qlockify',
@@ -41,7 +42,7 @@ const projectDefinitions: ProjectDefinition[] = [
id: 'guilan-ace',
link: 'https://east-guilan-ce.ir/',
tags: ['Django Ninja', 'Next.js', 'Docker'],
image: '/images/project-guilan-ace.png',
image: portfolioImage('/images/project-guilan-ace.png', 359, 200),
content: {
en: {
title: 'Guilan ACE',
@@ -61,7 +62,7 @@ const projectDefinitions: ProjectDefinition[] = [
id: 'zarinpal',
link: 'https://zarinpal.amiirkhl.ir/',
tags: ['Go', 'SvelteKit', 'Docker'],
image: '/images/project-zarinpal.png',
image: portfolioImage('/images/project-zarinpal.png', 359, 200),
content: {
en: {
title: 'Zarinpal Analytic Dashboard',
@@ -81,7 +82,7 @@ const projectDefinitions: ProjectDefinition[] = [
id: 'iran-implant',
link: 'http://impinternal.com/',
tags: ['Django', 'Nginx', 'Docker', 'Label'],
image: '/images/project-iran-implant.png',
image: portfolioImage('/images/project-iran-implant.png', 359, 200),
content: {
en: {
title: 'Iran Implant',
@@ -100,7 +101,7 @@ const projectDefinitions: ProjectDefinition[] = [
{
id: 'interanet-solutions',
tags: ['Bash', 'Caddy', 'Docker', 'DevOps'],
image: '/images/project-interanet-solutions.png',
image: portfolioImage('/images/project-interanet-solutions.png', 1683, 935),
content: {
en: {
title: 'Interanet Solutions',
@@ -119,7 +120,7 @@ const projectDefinitions: ProjectDefinition[] = [
{
id: 'postgresql-job-queue',
tags: ['Go', 'SvelteKit', 'Docker'],
image: '/images/project-postgresql-job-queue.png',
image: portfolioImage('/images/project-postgresql-job-queue.png', 359, 200),
content: {
en: {
title: 'PostgreSQL Job Queue',
@@ -139,7 +140,7 @@ const projectDefinitions: ProjectDefinition[] = [
id: 'subsource',
link: 'http://subsource.net/',
tags: ['Product Design', 'Figma', 'Prototyping'],
image: '/images/project-subsource.png',
image: portfolioImage('/images/project-subsource.png', 2560, 1440),
content: {
en: {
title: 'Subsource',

View File

@@ -11,7 +11,7 @@ export interface SkillCategory {
export interface ToolItem {
id: string;
name: string;
icon: string;
icon: PortfolioImage;
}
export interface SkillDiscipline {
@@ -28,13 +28,13 @@ export interface FilterTab {
const tool = (id: string, name: string, icon = id): ToolItem => ({
id,
name,
icon: `/images/tools/${icon}.png`,
icon: portfolioImage(`/images/tools/${icon}.png`, 72, 72),
});
const placeholderTool = (id: string, name: string): ToolItem => ({
id,
name,
icon: `/images/tools/${id}.png`,
icon: portfolioImage(`/images/tools/${id}.png`, 72, 72),
});
export const skillsByDiscipline: Record<DisciplineId, SkillDiscipline> = {
@@ -241,3 +241,4 @@ export const filterTabs: FilterTab[] = [
{ id: 'product-management', label: 'Product Management' },
{ id: 'design-ux', label: 'Design & UX' },
];
import { portfolioImage, type PortfolioImage } from '@/types/media';