Personal portfolio and blog of Sander de Snaijer, a frontend developer from the Netherlands with 10+ years of experience.
This site is where code meets creativity: webcam-controlled browser games built with MediaPipe, 3D printing experiments with resin and Arduino, Flutter apps on the App Store, and tutorials about all of it. Built as a fully SEO-optimized Next.js application with structured data, dynamic sitemaps, and tag-based content hubs.
Live at: www.sanderdesnaijer.com
- Projects / face-controlled Tetris, webcam Duck Hunt with hand tracking, a 3D-printed Arduino word clock, a multi-language Sudoku app, geospatial weather tools, and more
- Blog / step-by-step guides on 3D printing with resin, MediaPipe hand and face tracking in the browser, building with Next.js and Sanity, Flutter app development, and creative coding
- Tags / 50+ topic pages that group projects and articles by technology (React, Flutter, MediaPipe, Arduino, 3D printing, etc.)
- Next.js 16 / App Router, static generation with ISR, dynamic metadata
- TypeScript / end-to-end type safety
- Sanity / headless CMS with real-time previews and structured content
- Tailwind CSS / utility-first styling
- Playwright / end-to-end testing
- Jest / unit testing
- Vercel / deployment with preview branches
- JSON-LD structured data (BlogPosting, FAQPage, Person, WebSite, BreadcrumbList, SoftwareApplication)
- Open Graph and Twitter Card meta tags with optimized images
- Dynamic sitemap generation
- Tag hub pages with unique descriptions and intro content
- Content-first title strategy for better SERP click-through rates
- FAQ schema on blog posts for rich result eligibility
Copy .env.example to .env.local and fill in the required values:
cp .env.example .env.localThen run the development server:
npm run devOpen http://localhost:3000 to see the result.
app/ Next.js App Router pages, components, and utilities
sanity/ Sanity schema definitions and studio config
e2e/ Playwright end-to-end tests, fixtures, and snapshot tooling
messages/ i18n translation files
public/ Static assets and favicons
redirects/ Blog redirect mappings (old Medium slugs)
scripts/ Build and migration scripts
E2E tests run against local JSON fixtures instead of live Sanity, making them offline, deterministic, and free of API usage. After publishing new content in Sanity, update the fixtures:
npx tsx e2e/scripts/snapshot-fixtures.tsThis pulls current production data and writes it to e2e/fixtures/. Commit the updated fixtures so CI uses them too.
See .env.example for all required variables. You'll need credentials for Sanity, Vercel, and Google site verification.
Export a blog post from Sanity to dev.to-compatible markdown:
npm run export:devto -- <slug>List all available blog post slugs:
npm run export:devtoThe exported markdown file lands in scripts/devto-exports/<slug>.md with frontmatter, cover image, tags, and canonical URL pre-filled. Review the content and paste into dev.to/new. Set published: true when ready to go live.
The site exposes a full-content RSS 2.0 feed at /feed.xml (app/feed.xml/route.ts). Each item includes the complete article as HTML in <content:encoded>, rendered from the Sanity Portable Text body, plus title, link, canonical guid, publish date, author, tags, and the cover image as an enclosure. The feed is statically generated and refreshed on-demand: the Sanity webhook (/api/revalidate) revalidates /feed.xml when a blog post or tag changes, with a 24h time-based backstop. A discovery <link rel="alternate" type="application/rss+xml"> is advertised on all public pages.
HackerNoon imports from the RSS feed above (its Editor 2.0 has no reliable markdown paste import). Go to app.hackernoon.com/new, hover "Import Story", choose RSS Feed, and point it at https://www.sanderdesnaijer.com/feed.xml (or use Direct URL with a single post link). Set the canonical URL in Story Settings and submit for review.
The site supports IndexNow for instant URL submission to Bing, Yandex, and other participating search engines. The verification key is served from public/.
Submit all pages from the sitemap:
node scripts/indexnow.mjsSubmit specific URLs after publishing new content:
node scripts/indexnow.mjs https://sanderdesnaijer.com/blog/my-new-postRun once per publish. Don't resubmit the same URLs repeatedly.
MIT