Modern founder-style personal portfolio for Mus'ab Kamaldinov, also known as zetfri.
Built with:
- Next.js App Router
- TypeScript
- Tailwind CSS
- Framer Motion
- next-intl
- Hygraph CMS
- GraphQL
- Hygraph Rich Text Renderer
Install dependencies:
npm installRun the development server:
npm run devOpen http://localhost:3000/en.
The default language is English. The proxy redirects / to /en, and every route is available in English and Russian:
/enand/ru/en/aboutand/ru/about/en/projectsand/ru/projects/en/projects/[slug]and/ru/projects/[slug]/en/blogand/ru/blog/en/blog/[slug]and/ru/blog/[slug]/en/servicesand/ru/services/en/contactand/ru/contact
Copy the example file:
cp .env.local.example .env.localThen add your Hygraph values:
HYGRAPH_ENDPOINT=
HYGRAPH_TOKEN=
MONGODB_URI=
MONGODB_DB_NAME=zetfri
TELEGRAM_BOT_TOKEN=
TELEGRAM_CHAT_ID=HYGRAPH_TOKEN is optional if your Hygraph API is public.
MONGODB_URI and MONGODB_DB_NAME power the blog and project view counters.
TELEGRAM_BOT_TOKEN and TELEGRAM_CHAT_ID power the /contact form through Telegram Bot API.
View counters are stored in the viewCounters collection with this shape:
{
id: string;
blogId: string;
counter: number;
users: {
id: string;
userId: string;
}[];
}Create these models in Hygraph:
titleEn: Single line texttitleRu: Single line textslug: SlugexcerptEn: Multi-line textexcerptRu: Multi-line textcoverImage: Assetcategory: Reference to Categorytags: Multiple references to Tagauthor: Reference to AuthorcontentEn: Rich TextcontentRu: Rich TextpublishedAt: DatereadingTime: Numberfeatured: BooleanseoTitleEn: Single line textseoTitleRu: Single line textseoDescriptionEn: Multi-line textseoDescriptionRu: Multi-line textstatus: Enumeration
Status values:
DraftPublishedArchived
nameslugdescriptioncolor
nameslug
namerolebioavataremailtelegraminstagramgithub
The blog loader is prepared for multilingual Hygraph posts. English routes use *En fields, Russian routes use *Ru fields, and missing localized content falls back to the other language.
Local portfolio content lives in:
data/portfolio.tsdata/projects.tsdata/services.tsdata/skills.tsdata/mock-blog-posts.ts
Hygraph blog queries live in:
lib/queries.tslib/hygraph.ts
The blog uses Hygraph first. If Hygraph is not configured, unavailable, or has no published posts yet, it falls back to the local mock posts in data/mock-blog-posts.ts.
UI translations live in:
messages/en.jsonmessages/ru.json
Locale routing lives in:
i18n/routing.tsi18n/request.tsi18n/navigation.tsproxy.ts
When adding visible text, put it in the matching translation namespace instead of hardcoding it in a component. Project data, service data, skill groups, and mock blog posts use localized fields in the data files, then helper functions return the right language for the current route.
To edit project translations, update data/projects.ts. To edit the mock blog post translations, update data/mock-blog-posts.ts.
npm run dev
npm run build
npm run start
npm run typecheckDeploy on Vercel and add the same environment variables in the Vercel project settings.