Zum Hauptinhalt springen
tsecurity.de LIVE
Echtzeit-Radar & Feeds
Alle RSS Feeds
👥 Community & Social
Sichere ProgrammierungW3C IRC bots now open source(21.09.2026 um 13:13 Uhr)
Sichere ProgrammierungThe Weakest Leg(21.09.2026 um 08:30 Uhr)
Sichere ProgrammierungHow to Use st-core.fscss with Svelte (Compiled)(21.09.2026 um 13:00 Uhr)
Sichere ProgrammierungBuilding a Pre-Trade Oracle Safety Check for DeFi Agents(21.09.2026 um 13:03 Uhr)
Sichere ProgrammierungYour Finance Agent Needs an Evaluation Harness, Not Just a Prompt(21.09.2026 um 13:05 Uhr)
Server SecurityDatenleck bei GUTcert: Sorge um Energienetz-Geheimnisse(21.09.2026 um 12:30 Uhr)
Sichere ProgrammierungW3C IRC bots now open source(21.09.2026 um 13:13 Uhr)
Sichere ProgrammierungThe Weakest Leg(21.09.2026 um 08:30 Uhr)
Sichere ProgrammierungHow to Use st-core.fscss with Svelte (Compiled)(21.09.2026 um 13:00 Uhr)
Sichere ProgrammierungBuilding a Pre-Trade Oracle Safety Check for DeFi Agents(21.09.2026 um 13:03 Uhr)
Sichere ProgrammierungYour Finance Agent Needs an Evaluation Harness, Not Just a Prompt(21.09.2026 um 13:05 Uhr)
Server SecurityDatenleck bei GUTcert: Sorge um Energienetz-Geheimnisse(21.09.2026 um 12:30 Uhr)
Intelligence View
⚡ tsecurity.de Intelligence

How I Made My Portfolio Infinitely Extendable with 4 Lines of JSON

Adding a new project to my portfolio takes 30 seconds. No component changes. No new routes. No layout adjustments. Just 4 lines of JSON and an image dropped into a folder. I got tired of the typical portfolio maintenance cycle: build…

0
↗ Quelle (dev.to)
Reagiere als Erste:r — dein Feedback zählt!

Adding a new project to my portfolio takes 30 seconds. No component changes. No new routes. No layout adjustments. Just 4 lines of JSON and an image dropped into a folder.



I got tired of the typical portfolio maintenance cycle: build something cool, then spend an hour wiring it into your portfolio site, adjusting layouts, making sure the new card doesn't break the grid. So I designed mine to be completely data-driven from the start.



Here's how it works.






The Single Source of Truth



Every project on my portfolio lives in one file: projects.js. Each project is an object with exactly 4 fields:




{
id: 'crystal-index',
title: 'Crystal Index',
techStack: 'TypeScript, Next.js, Prisma, SQL, GPT4, React 3 Fiber',
info: 'Custom CMS for cataloguing crystals with structured filters for colour, chakra, and properties, and GPT-4-generated descriptions.',
}






That's it. Four lines. The entire portfolio renders from an array of these objects.






The ID Does Triple Duty



The id field is where the design gets interesting. It's not just an identifier. It serves three purposes simultaneously:



1. GitHub link path. The portfolio constructs repository URLs by appending the ID to a base GitHub URL. crystal-index becomes https://github.com/sammii-hk/crystal-index. No separate URL field needed.



2. Image filename lookup. An auto-generated image map resolves the ID to the correct image file and extension. crystal-index maps to /assets/images/crystal-index.jpg. No manual image path configuration.



3. React key. The ID serves as the unique key when mapping over the array. Standard React pattern, but it comes free because the ID already exists for the other two purposes.



One field, three jobs. This means there's no redundant data, no fields that can fall out of sync, and no opportunity for a GitHub link to point to the wrong repo because the image and the link derive from the same source.



For projects under a GitHub organisation, the ID includes the org path: unicorn-poo/succulent. The image utility handles this by splitting on / and taking the last segment for the filename lookup, while the full path constructs the correct GitHub URL.






Auto-Generated Image Map



I didn't want to manually track whether each project screenshot is a .jpg or .png. So I wrote a build script that scans the images directory and generates a JSON map:




import { readdir, writeFile } from 'fs/promises';
import { join } from 'path';

const imagesDir = join(process.cwd(), 'public', 'assets', 'images');

async function generateImageMap() {
const files = await readdir(imagesDir);
const imageMap = {};

files.forEach(file => {
if (file === 'sammii.png') return;
const name = file.replace(/\.(jpg|png)$/, '');
const ext = file.endsWith('.png') ? 'png' : 'jpg';
if (!imageMap[name]) imageMap[name] = ext;
});

const outputPath = join(process.cwd(), 'app', 'common', 'utils', 'image-map.json');
await writeFile(outputPath, JSON.stringify(imageMap, null, 2));
}

generateImageMap();






The output is a simple lookup:




{
"crystal-index": "jpg",
"lunary": "png",
"succulent": "png",
"day-lite": "jpg"
}






Then a utility function resolves any project ID to its full image path:




import imageMapData from './image-map.json';
const imageMap = imageMapData;

export const getImagePath = (projectId) => {
const projectBaseId = projectId.split('/').pop() || projectId;
const extension = imageMap[projectBaseId] || 'jpg';
return `/assets/images/${projectBaseId}.${extension}`;
};






Drop an image in the folder, run the script, done. The portfolio picks it up automatically.






Zero Component Changes



The portfolio has two completely different view modes: a responsive grid with click-to-expand modals, and a full-screen vertical carousel. Both consume the exact same projects array.



The grid view maps over the array and renders cards:




{projects.map((project) => (
<div key={project.id} onClick={() => setSelectedProject(project)}>
<ProjectItem project={project} isGrid={true} />
</div>
))}






The carousel view maps over the same array and renders full-width slides:




<Carousel
items={projects}
renderItem={({ item, isSnapPoint }) => (
<CarouselItem key={item.id} isSnapPoint={isSnapPoint}>
<ProjectItem project={item} />
</CarouselItem>
)}
/>






ProjectItem accepts an isGrid prop that switches between compact card layout and expanded layout. Same component, same data, two presentations. Adding a project to the array means it automatically appears in both views with no additional work.






The 30-Second Workflow



When I finish a new project, here's what I actually do:




  1. Take a screenshot

  2. Drop it in /public/assets/images/ as project-name.png

  3. Run node scripts/generate-image-map.mjs

  4. Add 4 lines to projects.js

  5. Push to GitHub



The portfolio rebuilds and the new project appears in both views, with the correct image, the correct GitHub link, and the correct layout. Five steps, thirty seconds, zero component files touched.






The Philosophy Behind It



This isn't just about saving time on a portfolio. It's a pattern I use everywhere.



My astrology app Lunary has a grimoire with over 2,000 articles. They're stored as structured data and rendered through shared components. Adding a new article about a crystal or a tarot card doesn't require touching any UI code.



My publishing tool Spellcast manages multiple social media accounts and platforms. Account configurations are data objects. Adding a new platform means adding to the config, not rebuilding the interface.



The principle is always the same: separate data from presentation. Make the data structure do the heavy lifting. Keep the components generic enough that they never need to know about specific content.



If you're spending more time wiring new content into your portfolio than building the projects themselves, you've got the architecture backwards. Make your portfolio work for you, not the other way around.






I'm Sammii, founder of Lunary, an astrology app that actually teaches you to read your own birth chart. I write about the technical decisions behind building products as a solo developer. Follow along on Dev.to or check out the code on GitHub.

Ähnliche Beiträge
🔍 Verwandte News

Auch interessante Nachrichten How I Made My Portfolio Infinitely Extendable with 4 Lines of JSON

Thematisch verwandte Begriffe: Made, Portfolio, Infinitely, Extendable · 6 Treffer

Laden...

Videos werden geladen ...

Laden...

Beiträge werden geladen ...

Laden...

Videos werden geladen ...

Laden...

Beiträge werden geladen ...

Laden...

Videos werden geladen ...

Laden...

Beiträge werden geladen ...

Laden...

Videos werden geladen ...

Laden...

Beiträge werden geladen ...

Laden...

Videos werden geladen ...

Zum Aktualisieren ziehen
ZERO-DAY CVE-2026-94040 | A flaw has been found in vas3k TaxHacker up to 0.8.5. Affected by this v…
Advisory →
TTS Reader • tsecurity.de Voice
tsecurity.de Icon
tsecurity.de App
Offline-Lesen, Eilmeldungen & 0ms Ladezeit

Installiere tsecurity.de direkt auf deinen Home-Bildschirm für das ultimative Vollbild-Magazinerlebnis ohne Browser-Leisten.

Nächster Beitrag
Themen-Radar & Intelligence Matrix
Echtzeit-Taxonomie nach Angriffsvektoren & Plattformen

tsecurity.de Live Threat Radar

🔴 LIVE RADAR
MONITORING
AKTIV
CVE-DATENBANK
LIVE
🔍
Community Radar & Live Chat
Sentinel Bot online • Live-Stream
Dein Cluster: Security Explorer
Match:
lädt…
Verbindung zum Community-Stream wird aufgebaut...
Bearbeitungsmodus — Senden überschreibt deine Nachricht
Community-Puls — was gerade passiert
lädt…
Aktivitäten deiner Analysten
lädt…
Neues Thema oder Eilmeldung einreichen

Reiche interessante Links, Zero-Days oder Debatten ein. Die Community entscheidet per Upvote über die Veröffentlichung.

Heiß diskutierte Einreichungen
🔖 Gespeicherte Artikel
📂 Keine gespeicherten Artikel vorhanden.
Zurück Ziehen Vor
Links: vorheriger Artikel Rechts: nächster Artikel unten: schließen
News NIS-2 Frühwarnung Tier-1 Intel ⏱️ 3 Min vor 10 Min
Artikeldaten werden geladen...

Zurück: vorheriger Vor: nächster
↗ Original-Quelle
Social Reaktionen Deine Reaktion zählt
Einstufung & Relevanz-Poll 0 Stimmen
In sozialen Netzwerken teilen 1-Klick