- Maintaining & Extending
7. Maintaining & Extending
Part 7 of the From CV to Site tutorial series.
Prerequisites
- Completed Tutorial 6
- Site is live at your custom domain
Scope
This tutorial covers routine maintenance, common extensions, and operational practices. It does not introduce new architecture.
Regular maintenance tasks
Adding a publication
- Add the entry to
content/cv.docxfollowing the APA 7 format required by the parser - Run
./update-cv.shat the project root - Review the diff in
src/data/publications.tsandpublic/cv/publications.bib - Commit and push:
git add content/cv.docx src/data/ public/cv/
git commit -m "Add publication: [short title]"
git push
Vercel redeploys automatically.
Adding a news entry
News items are typically stored as a TypeScript array. Edit src/data/news.ts:
export const news = [
{
date: "2026-04-20",
title: "Keynote at Conference Name",
summary: "Short description.",
link: "https://example.com/optional-link",
},
// ...existing entries
] as const;
Newest entries go first. Commit and push.
Updating project status
Projects with defined start and end dates can be filtered in code (active vs. completed). For status changes (e.g., “proposed” to “funded”), update the entry in the CV or src/data/projects.ts directly.
If you keep status on each project, filter in the page component:
const active = projects.filter((p) => p.status === "active");
const completed = projects.filter((p) => p.status === "completed");
Updating personnel
Edit src/data/people.ts:
export const people = [
{
name: "Full Name",
role: "Ph.D. Student",
startYear: 2024,
endYear: null,
bio: "Short bio.",
photo: "/people/name.jpg",
links: { scholar: "https://...", github: "https://..." },
},
] as const;
Add photos to public/people/ at a consistent size (e.g., 400x400, compressed).
Adding new pages
To add a new route, for example /writing:
- Create
src/app/writing/page.tsx - Add the route to the header navigation in
src/components/Header.tsx - If the page needs data, create
src/data/writing.ts - Commit and push
Use an existing page as a template to maintain typographic and layout consistency.
Handling dependency updates
Review dependencies quarterly. Two levels of update are common:
Patch and minor updates:
pnpm update
pnpm dev # verify locally
Major updates (Next.js, React, Tailwind):
pnpm outdated
pnpm update next react react-dom --latest
Read the relevant changelog before major updates. For Next.js, the upgrade guide lists required code changes per release. Test the dev server and key routes before pushing.
Monitoring the deployment
Vercel’s Deployments tab shows build status and logs. Check it after every push.
For uptime and performance, Vercel Analytics (free on Hobby) is sufficient for academic sites. Enable it at Settings > Analytics.
If you want external monitoring, services such as UptimeRobot offer free checks at 5-minute intervals.
Backup strategy
Your Git repository on GitHub is the primary backup. Additional practices:
- Keep a local clone on a secondary machine or external drive
- Ensure
content/cv.docxis tracked in Git (do not add it to.gitignore) - Mirror the repository to a second host (for example, GitLab) if desired:
git remote add mirror git@gitlab.com:YOUR_USERNAME/my-site.git
git push mirror main
Performance considerations
Next.js generates static HTML at build time by default, which is optimal for academic sites. To keep pages fast:
- Compress images before adding them to
public/. Use Squoosh or thesharpCLI for JPEG, PNG, and WebP conversion - Use the
Imagecomponent fromnext/imagefor photos that appear above the fold - Avoid adding client-side JavaScript where static HTML will suffice (no
"use client"directive unless required)
Accessibility
Verify basic accessibility at each update:
- All images have
alttext - Heading hierarchy is sequential (no skipping levels)
- Contrast passes WCAG AA (4.5:1 for body text)
- Pages are navigable by keyboard
Use Chrome’s built-in Lighthouse (DevTools > Lighthouse) for a quick audit.
Extending the CV parser
The parser in Tutorial 5 handles APA 7 journal articles. To support additional formats:
- Add a new parser file in
scripts/parsers/ - Extend
ENTRY_REGEXor add a branch for the new format - Validate parsed entries with a Zod schema
- Register the new section in
scripts/parse-cv.ts
Example: adding a preprints section
Preprints (arXiv, OSF, SSRN) do not include a journal and often use a different identifier pattern. Adding support requires three short changes.
1. New parser at scripts/parsers/preprints.ts:
import { z } from "zod";
import { htmlToLines, stripTags } from "./shared";
const PreprintSchema = z.object({
authors: z.array(z.string()).nonempty(),
year: z.number().int(),
title: z.string().min(1),
repository: z.enum(["arXiv", "OSF", "SSRN", "bioRxiv", "other"]),
identifier: z.string().min(1),
url: z.string().url().optional(),
});
export type Preprint = z.infer<typeof PreprintSchema>;
const ENTRY_RE =
/^(?<authors>.+?)\s*\((?<year>\d{4})\)\.\s*(?<title>.+?)\.\s*(?<repo>arXiv|OSF|SSRN|bioRxiv):\s*(?<id>[\w./-]+)\.?\s*(?<url>https?:\/\/\S+)?\s*$/i;
export function parsePreprints(html: string) {
const items: Preprint[] = [];
const unparsed: string[] = [];
for (const line of htmlToLines(html)) {
const m = ENTRY_RE.exec(stripTags(line));
if (!m?.groups) { unparsed.push(stripTags(line)); continue; }
const g = m.groups;
const parsed = {
authors: g.authors.split(/,\s*&\s+|\s+&\s+/).map((a) => a.trim()),
year: Number(g.year),
title: g.title.trim(),
repository: g.repo as Preprint["repository"],
identifier: g.id,
url: g.url,
};
const res = PreprintSchema.safeParse(parsed);
if (res.success) items.push(res.data); else unparsed.push(stripTags(line));
}
return { items, unparsed };
}
2. Register the section in scripts/parse-cv.ts:
import { parsePreprints } from "./parsers/preprints";
// Add to the configs array:
{
keys: ["preprints", "workingpapers"],
parse: parsePreprints,
outFile: "preprints.ts",
exportName: "preprints",
typeName: "Preprint",
},
3. Add the type to src/data/types.ts (use the same Preprint type, exported from the types module), and create src/data/preprints.ts as a generated file stub following the pattern of the others.
After these changes, a CV section headed Preprints with entries like Doe, J., & Smith, A. (2025). Title. arXiv: 2501.12345. https://arxiv.org/abs/2501.12345. parses correctly.
Other formats worth adding
- Book chapters: parse editors, publisher, and chapter page range
- Conference proceedings: distinguish from journal articles (often no volume or DOI)
- Software / datasets: capture version, repository URL, and license
- Non-English titles: no code change required, but verify
lang="en"on the root layout does not interfere with screen readers for those sections
Working with Claude Code for maintenance
Claude Code remains useful for routine tasks:
- Generating a new page from a design brief
- Debugging build errors
- Extending the CV parser to a new entry format
- Migrating to a newer Next.js major version
Keep your design brief and relevant files open in the session. Claude Code performs best with concrete context.
When to seek help
Use the following resources before asking elsewhere:
| Question type | Resource |
|---|---|
| Next.js routing, data fetching, builds | Next.js documentation |
| Tailwind utility classes | Tailwind documentation |
| Vercel deployment and DNS | Vercel documentation |
| Claude Code features | Claude Code documentation |
| Bugs or unclear steps in this guide | Issues on the repository |
Conclusion
At this point, you have:
- A live academic website at a custom domain
- A CV-based workflow for publications, projects, fellowships, talks, and teaching
- A Git-based deployment pipeline through Vercel
- A documented design brief that guides future changes
- The skills to add pages, update content, and extend the site without external help
Subsequent changes are variations on the patterns covered in this series. Return to specific tutorials as needed.
Questions or feedback: open an issue on the repository or email hannahlee@virginia.edu.