Redesigning My Portfolio and Leveling Up with TypeScript + ESLint

by kleamerkuri

I recently gave my portfolio site a visual glow-up that included a layout refresh and more intentional UX.

I wanted something more curated. Something that felt thought-through, both in its design and its code.

Not that the prior version wasn’t functional, it had all the essentials—projects, a thorough resume section with its own timeline, and a personable about page—but lacked finesse.

More importantly, it needed to reflect the kind of dev I’ve grown into. One who’s constantly trying to polish her UI skills while advancing more of the backend expertise.

So it came as a natural second step to give my new portfolio a proper technical polish too 💅

The goal? Clean up the design, make it easier to browse, and tighten up the codebase by adding TypeScript and ESLint. Overall, a backend dev upgrade in the form of stricter typing and formatting rules.

This post breaks down what I changed, why I changed it, and how I handled the technical upgrades.

If you’ve ever looked at your portfolio and thought, “What can I do to make it so much better,” this one’s for you.

Related: How To Build An Eye-Catching Personal Portfolio Webpage

Design and Layout Updates

The objective was to update the aesthetic and organization of my existing portfolio website.

I borrowed inspiration from a few places, mainly Brittany Chiang’s portfolio and how LinkedIn presents user content.

These touches made a huge difference in making the site feel more intentional. Instead of “Here’s a bunch of stuff I did,” it’s “Here’s what I’m most proud of. Want more? Here’s where to look.”

Breaking Down the New Aesthetic

  • Spotlight Effect on Projects: On hover, projects subtly light up while others dim. This helps focus attention, and I also disable interactions with non-featured projects—keeps things clean.
  • Two-Column Layout: Borrowed from Brittany’s setup—left sidebar with links and essentials, right side for primary content. It makes navigation smoother and feels more like a dashboard than a blog. Plus, the left sidebar borrows from LinkedIn’s profile display to add some personality.
  • Featured Content on Home: Not everything makes the front page. Just the highlights: a couple of featured projects, recent writing, and some quick info. Everything else is still accessible, just tucked away.
  • Archive Page: A single, paginated archive page with filters. Helps users easily find my work by category without digging.
  • Extended Resume: In addition to a static, downloadable PDF, I added an interactive resume—clickable sections, skill tags, job-specific write-ups.
  • Publications & Product Links: Where relevant, I now link to apps, features, and publications I’ve contributed to—just like LinkedIn does with “Featured” items.

At the end, the portfolio site itself is a project showcasing current design vibes and a recent focus on clean and intentional UI development.

Before (left), After (right). Different aesthetics

Development Upgrades

Now let’s get into the technical side of things.

I’ve always been a fan of clean code, but until recently, my portfolio was running plain JavaScript with the default Gatsby ESLint setup.

Functional, yes. Robust? Not really.

So I decided to add TypeScript and properly configure ESLint with Prettier.

Step-by-Step: Adding TypeScript

1. Install TypeScript and Type Definitions

First up, I installed TypeScript and the necessary type packages:

npm add -D typescript @types/node @types/react @types/react-dom

This sets the foundation for everything else. The @types packages help TypeScript understand the shape of Node.js and React elements.

2. Create a tsconfig.json

This file lives at the root and tells TypeScript how to interpret your code.

{
  "compilerOptions": {
    "target": "ES6",
    "module": "ESNext",
    "jsx": "preserve",
    "moduleResolution": "node",
    "strict": true,
    "esModuleInterop": true,
    "skipLibCheck": true
  },
  "include": ["src"],
  "exclude": ["node_modules"]
}

I kept it minimal for now, with a few tweaks to match Gatsby’s structure.

The key option here is "strict": true. That enables the stricter rules, like preventing undefined assignments unless explicitly allowed.

3. Rename Files to .ts or .tsx

Any file that exports JSX now ends in .tsx. This is important—otherwise, TypeScript throws a fit when it sees JSX in a .ts file.

Example:

  • Button.jsButton.tsx
  • utils.jsutils.ts

I went one component at a time. It wasn’t a huge lift, but it did require defining types for props, function return values, and variables.

Tip
If you’re new to Typescript, start with a small scale application so you can get comfortable working with defined types. It’s easier learning the ropes when you don’t feel overwhelmed!

4. Define Interfaces in One Place

I created a interfaces.ts helper file for reusable type definitions. Here’s an example from a Modal component:

interface IModalProps {
  open?: boolean;
  setOpen: (isOpen: boolean) => void;
  project: IProject;
}

And the IProject interface looks like:

export interface IProject {
  title: string;
  description: string;
  image: string;
  links: {
    demo: string;
    blog: string;
  };
  meta: {
    category: string[];
    date: string;
    stack: string[];
  };
}

It might seem like extra work, but this made a huge difference!

In the past, I’d find myself digging into the project data just to figure out what properties existed. Now, with interfaces, I get autocomplete and clear definition suggestions.

Every project now has to include title, description, meta, etc.—no guesswork. And if any property is optional, a change only needs to be made in the type definition by chaining a question mark like so: image?: string.

Setting Up ESLint + Prettier

Gatsby ships with ESLint built in, but if you want to customize it, you’ll need to add your own setup.

1. Install ESLint and TypeScript Plugins

npm install --save-dev eslint @typescript-eslint/parser @typescript-eslint/eslint-plugin

These packages give ESLint the tools it needs to parse .ts and .tsx files and understand TypeScript-specific rules.

2. Configure ESLint Rules

I created a .eslintrc.js at the root:

module.exports = {
  parser: '@typescript-eslint/parser',
  plugins: ['@typescript-eslint', 'import'],
  extends: [
    'eslint:recommended',
    'plugin:@typescript-eslint/recommended',
    'plugin:prettier/recommended',
  ],
  rules: {
    'prettier/prettier': ['error'],
    'import/order': [
      'error',
      {
        groups: ['builtin', 'external', 'internal'],
        'newlines-between': 'always',
        alphabetize: { order: 'asc', caseInsensitive: true },
      },
    ],
    '@typescript-eslint/no-unused-vars': ['error'],
  },
  settings: {
    'import/resolver': {
      typescript: {},
      node: {
        extensions: ['.js', '.jsx', '.ts', '.tsx'],
      },
    },
  },
};

A few rules I care about:

  • No unused variables
  • Alphabetized imports, grouped with newlines
  • Enforced Prettier formatting (single quotes, semicolons, etc.)

Note 👀
This is by no means the completed configuration but it’s enough to lay down the groundworks so we can build later as we get new formatting requirements.

3. Add Scripts

In package.json, I added:

"scripts": {
  "lint": "eslint '**/*.{js,jsx,ts,tsx}'",
  "lint:fix": "eslint '**/*.{js,jsx,ts,tsx}' --fix",
  "format": "prettier --write '**/*.{js,jsx,ts,tsx,json,css,md}'"
}

This lets me run npm run lint:fix anytime I want to clean things up or npm run format to run prettier.

Having the commands as scripts is the easier approach since you don’t need to worry about remembering the commands every time you want to run the linter or formatter.

4. Configure Prettier

I dropped in a .prettierrc.json:

{
  "singleQuote": true,
  "semi": true,
  "trailingComma": "all",
  "printWidth": 80,
  "arrowParens": "always"
}

Nothing fancy—just consistent formatting.

Also added .eslintignore and .prettierignore files:

node_modules/
public/
.cache/
dist/

Tip: Make sure to add the ignore files or all files will be linted and formatted which is a nightmare in itself 😵

5. ESLint Import Fixes

At one point, I hit a super annoying error:

Missing file extension for "../components/..."

Turns out I had to explicitly define resolver settings (despite including rules for import/extensions):

"settings": {
  "import/resolver": {
    "typescript": {},
    "node": {
      "extensions": [".js", ".jsx", ".ts", ".tsx"]
    }
  }
}

That cleared things up. Know that working with ESLint isn’t a walk in the park—there’s a learning curve and the more you use the linter, the more comfortable you’ll become.

Explore: How To Create A Responsive Landing Page With Bootstrap: 15 Essential Tips

What I Learned

  • TypeScript forced me to clean up my components and be more intentional with my data shapes.
  • ESLint + Prettier kept everything uniform, even when switching between personal and work projects.
  • Design-wise, small things like spacing, interaction states, and clear visual hierarchy go a long way.

It’s one of those projects where you think it’ll only take a weekend, and two weeks later, you’re still tweaking the font weight on hover. But honestly? Totally worth it.

More: How To Build A Responsive Product Landing Page Using Figma CSS

As for next steps, I’m thinking it’ll be an interesting undertaking adding an AI chatbot as a portfolio guide so we can dip our toes into some AI integration!

What do you think?

It’s a Wrap

This wasn’t as much a portfolio redesign (for that, check out the previous version of my portfolio site)—it was a reset. The kind where you take stock of how far you’ve come, clean up your code, and make sure your site reflects what you can actually do now.

If you’re thinking about upgrading your site, do it. Whether it’s visual design, accessibility, or better developer tools, that effort pays off in the long run.

Got questions? Want to see more code examples or compare config files? Drop me a message below or check out my portfolio repo on GitHub for the complete code.

Until I see you next, keep coding 🙂

Related Posts