From 0e45270d938873690e7e278fc36068e057717ef2 Mon Sep 17 00:00:00 2001 From: Prad Nukala Date: Tue, 30 Jun 2026 14:28:50 -0400 Subject: [PATCH] feat(about): add personal bio and details to about page --- src/pages/about.astro | 72 ++++++++++++++++++++-------- src/pages/experience/[...slug].astro | 12 ++++- src/pages/experience/index.astro | 16 +++++++ src/pages/index.astro | 43 +++++++---------- src/pages/og-image/[...slug].png.ts | 4 +- src/pages/projects/[...page].astro | 13 +++++ src/pages/projects/[...slug].astro | 16 ++++++- src/pages/rss.xml.ts | 2 +- src/pages/speaking/[...page].astro | 13 +++++ src/pages/speaking/[...slug].astro | 41 ++++++++++------ src/pages/tags/index.astro | 31 ------------ src/pages/writing/[...page].astro | 13 +++++ src/pages/writing/[...slug].astro | 11 +++-- src/site.config.ts | 19 ++++---- src/types.ts | 3 ++ 15 files changed, 200 insertions(+), 109 deletions(-) delete mode 100644 src/pages/tags/index.astro diff --git a/src/pages/about.astro b/src/pages/about.astro index bae7f97..b3023ea 100644 --- a/src/pages/about.astro +++ b/src/pages/about.astro @@ -2,35 +2,69 @@ import PageLayout from "@/layouts/Base.astro"; const meta = { - description: "I'm a starter theme for Astro.build", + description: + "About Prad Nukala — engineer and founder building decentralized identity infrastructure, W3C and DIF working-group member, and lifelong app developer.", title: "About", }; + +const organizations = [ + { + name: "World Wide Web Consortium (W3C)", + role: "Working Group — DIDs, WebAuthn, WASM", + url: "https://www.w3.org/", + }, + { + name: "Decentralized Identity Foundation (DIF)", + role: "Working Group — UCAN, DWN", + url: "https://identity.foundation/", + }, +]; ---

About

- Hi, I’m a starter Astro. I’m particularly great for getting you started with your own blogging - website. + I'm Prad Nukala — an engineer and founder building decentralized identity infrastructure. As + co-founder and CEO of Sonr, I'm + building a peer-to-peer identity and data network powered by decentralized identifiers (DIDs), + WebAuthn, and IPFS.

-

Here are my some of my awesome built in features:

-
    -
  • I'm ultra fast as I'm a static site
  • -
  • I'm fully responsive
  • -
  • I come with a light and dark mode
  • -
  • I'm easy to customise and add additional content
  • -
  • I have Tailwind CSS styling
  • -
  • Shiki code syntax highlighting
  • -
  • Satori for auto generating OG images for blog posts
  • -

- Clone or fork my repo if you like me! + I help shape the open standards behind user-owned identity as a working-group member at the + W3C (DIDs, WebAuthn, WebAssembly) and the Decentralized Identity Foundation (UCAN, DWN). I've + been shipping software since age 12 — three apps with over 1M downloads, technical writing + read 120k+ times, and bylines in Fast Company.

+ +

Education

+

+ Virginia Commonwealth University
+ B.S. in Applied Mathematics · 2017–2020 +

+

+ Attended 5 MLH hackathons, published 3 apps in 3 years with over 1M downloads, and wrote 3 + articles with over 120k reads. Coursework spanned linear algebra, number theory, topological + data analysis, and vector calculus. +

+ +

Honors & Recognition

+
    +
  • Guest Lecturer, Crypto Finance (15.492) — MIT Sloan School of Management
  • +
+ +

Standards & Organizations

+
    + { + organizations.map((org) => ( +
  • + + {org.name} + {" "} + — {org.role} +
  • + )) + } +
diff --git a/src/pages/experience/[...slug].astro b/src/pages/experience/[...slug].astro index 16dd831..877d077 100644 --- a/src/pages/experience/[...slug].astro +++ b/src/pages/experience/[...slug].astro @@ -3,8 +3,10 @@ import { render } from "astro:content"; import type { GetStaticPaths, InferGetStaticPropsType } from "astro"; import { Icon } from "astro-icon/components"; import PostPreview from "@/components/blog/PostPreview.astro"; +import TagList from "@/components/TagList.astro"; import { getAllExperience, getContentForExperience } from "@/data/experience"; import PageLayout from "@/layouts/Base.astro"; +import { breadcrumbList } from "@/utils/seo"; export const getStaticPaths = (async () => { const experiences = await getAllExperience(); @@ -20,12 +22,18 @@ type Props = InferGetStaticPropsType; const { exp, content } = Astro.props as Props; const { Content } = await render(exp); -const { startDate, endDate, organization, role, location, url } = exp.data; +const { startDate, endDate, organization, role, location, url, tags } = exp.data; const range = `${startDate.getFullYear()} – ${endDate ? endDate.getFullYear() : "Present"}`; const meta = { description: exp.data.description ?? `${role ?? ""} at ${organization}`.trim(), + keywords: tags, ogImage: `/og-image/experience/${exp.id}.png`, + schema: breadcrumbList([ + { name: "Home", path: "/" }, + { name: "Experience", path: "/experience/" }, + { name: exp.data.title, path: `/experience/${exp.id}/` }, + ]), title: exp.data.title, }; --- @@ -108,4 +116,6 @@ const meta = { ) } + + diff --git a/src/pages/experience/index.astro b/src/pages/experience/index.astro index a9ae52e..8f4c344 100644 --- a/src/pages/experience/index.astro +++ b/src/pages/experience/index.astro @@ -1,11 +1,27 @@ --- import { getAllExperience } from "@/data/experience"; import PageLayout from "@/layouts/Base.astro"; +import { breadcrumbList, collectionPageSchema } from "@/utils/seo"; const experiences = await getAllExperience(); const meta = { description: "Roles and organizations I've worked with", + schema: [ + breadcrumbList([ + { name: "Home", path: "/" }, + { name: "Experience", path: "/experience/" }, + ]), + collectionPageSchema({ + name: "Experience", + description: "Roles and organizations I've worked with", + path: "/experience/", + items: experiences.map((e) => ({ + name: e.data.role ? `${e.data.role}, ${e.data.organization}` : e.data.organization, + path: `/experience/${e.id}/`, + })), + }), + ], title: "Experience", }; diff --git a/src/pages/index.astro b/src/pages/index.astro index 688ef3e..3d52683 100644 --- a/src/pages/index.astro +++ b/src/pages/index.astro @@ -5,8 +5,10 @@ import Project from "@/components/project/Project.astro"; import { getAllPosts } from "@/data/post"; import { getAllSpeaking } from "@/data/speaking"; import PageLayout from "@/layouts/Base.astro"; -import { siteConfig, socialLinks } from "@/site.config"; +import { socialLinks } from "@/site.config"; +import { getTagIndex, getTaggedCollections } from "@/data/tags"; import { collectionDateSort, projectDateSort } from "@/utils/date"; +import { personSchema, websiteSchema } from "@/utils/seo"; const MAX_POSTS = 4; const allPosts = await getAllPosts(); @@ -15,36 +17,26 @@ const latestPosts = (allPosts.sort(collectionDateSort) as CollectionEntry<"writi MAX_POSTS, ); -const MAX_PROJECTS = 2; +const MAX_PROJECTS = 3; const allProjects = await getCollection("projects"); const latestProjects = allProjects.sort(projectDateSort).slice(0, MAX_PROJECTS); -const MAX_TALKS = 3; -const latestTalks = (await getAllSpeaking()).sort(collectionDateSort).slice(0, MAX_TALKS); +const talks = (await getAllSpeaking()).sort(collectionDateSort); + +const social = Object.fromEntries(socialLinks.map((s) => [s.friendlyName, s.link])); + +// Skills (from all tagged content) feed the Person `knowsAbout` signal. +const skills = [...getTagIndex(await getTaggedCollections()).values()].map((t) => t.label); const meta = { title: "Engineer & Founder", description: "Prad Nukala — engineer and founder building decentralized identity infrastructure. Explore my projects, experience, writing, and talks.", -}; - -const social = Object.fromEntries(socialLinks.map((s) => [s.friendlyName, s.link])); - -// Person structured data for richer search results. -const personSchema = { - "@context": "https://schema.org", - "@type": "Person", - name: siteConfig.author, - url: siteConfig.url, - jobTitle: "Engineer & Founder", - description: meta.description, - image: new URL("/avatar.png", siteConfig.url).href, - sameAs: socialLinks.filter((s) => !s.link.startsWith("mailto:")).map((s) => s.link), + schema: [websiteSchema(), personSchema(skills)], }; --- -