perf: optimize portfolio runtime delivery
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:
@@ -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');
|
||||
|
||||
@@ -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: {
|
||||
|
||||
@@ -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: {
|
||||
|
||||
@@ -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',
|
||||
|
||||
@@ -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';
|
||||
|
||||
Reference in New Issue
Block a user