Deploy a Portfolio Site to Vercel

Deploy a personal portfolio website to Vercel's global CDN.

Introduction

30 Second Summary

You've built something cool on your computer, but nobody else can see it yet. Getting your work from your laptop onto the internet is one of the most satisfying moments in a developer's journey.

In this project, you will deploy a personal portfolio website to Vercel, a cloud platform built for frontend deployments. You'll learn two ways to ship code to production: through a web dashboard and through your terminal.

What You'll Build

A responsive single-page portfolio site deployed to Vercel's global CDN, with automatic redeployments triggered by Git pushes.

By the end of this project, you'll have:

  • A live portfolio website accessible via a Vercel-generated .vercel.app URL.
  • A GitHub-connected deployment pipeline that automatically redeploys your site on every push.
  • Hands-on experience with two deployment methods: the Vercel Dashboard (GUI) and the Vercel CLI (terminal).
  • Secret Mission: Add a custom 404 page and configure URL redirects using a vercel.json configuration file.

Are there any prerequisites?

No prior experience with deployment or hosting is required. This guide includes full setup instructions for all tools you'll need, including Git, Node.js, and a Vercel account.

Build Your Portfolio Site

Your tools are installed and your accounts are ready. Now it's time to build the actual website you'll deploy to Vercel.

Every deployment needs source code. In this step, you'll create a clean, responsive portfolio page using HTML, CSS, and JavaScript.

In this step, get ready to:
  • Create a project folder with your HTML, CSS, and JavaScript files.
  • Build a single-page portfolio structure in HTML.
  • Add responsive styling and smooth-scroll interactivity.
Create the project folder and files

Before you can write any code, you need a dedicated folder to hold your website files. You'll create this on your Desktop so it's easy to find later.

  • Open your terminal by pressing Cmd+Space (macOS) or the Windows key (Windows), typing Terminal (macOS) or PowerShell (Windows), and pressing Enter.
  • Move to your Desktop by running this command:
cd ~/Desktop
  • Create your project folder and move into it by running these commands:
mkdir my-portfolio
cd my-portfolio
  • Create the three files your portfolio needs by running:
touch index.html style.css script.js
  • Confirm the files exist by running:
ls

You should see index.html, style.css, and script.js in the output.

Don't see all three files?

Make sure you ran the touch command from inside the my-portfolio folder. Run pwd to check your current location. If it doesn't end with my-portfolio, run cd ~/Desktop/my-portfolio and try the touch command again.

help me create my project files

  • Open the project in VS Code by running:
code .

What does this command do?

The code . command opens the current folder in Visual Studio Code. The dot (.) means "this directory." VS Code will open with your three empty files visible in the Explorer sidebar on the left.

Getting 'command not found' for code?

On macOS, you need to add VS Code to your PATH first. Open VS Code manually, press Cmd+Shift+P, type shell command, and select Shell Command: Install 'code' command in PATH. Restart your terminal and try again.

On Windows, the VS Code installer usually adds code to your PATH automatically. If not, reinstall VS Code and check the Add to PATH option during setup.

help me open my project in VS Code

Write the HTML structure

Your portfolio page needs a skeleton of content before you can style it. The HTML defines the sections visitors will see: a navigation bar, a hero header, an about section, project cards, and a contact footer.

  • In VS Code, click on index.html in the Explorer sidebar to open it.
  • Paste the following HTML into the file:
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>My Portfolio</title>
    <link rel="stylesheet" href="style.css">
</head>
<body>
    <nav id="navbar">
        <div class="nav-container">
            <a href="#" class="logo">Portfolio</a>
            <ul class="nav-links">
                <li><a href="#about">About</a></li>
                <li><a href="#projects">Projects</a></li>
                <li><a href="#contact">Contact</a></li>
            </ul>
        </div>
    </nav>

    <header id="hero">
        <h1>Hi, I'm <span class="highlight">Your Name</span></h1>
        <p>A developer learning to build and deploy web applications.</p>
    </header>

    <section id="about">
        <h2>About Me</h2>
        <p>I'm a developer passionate about building things for the web. I'm currently learning modern deployment workflows and cloud hosting with Vercel.</p>
    </section>

    <section id="projects">
        <h2>My Projects</h2>
        <div class="project-grid">
            <div class="project-card">
                <h3>Project One</h3>
                <p>A description of your first project goes here.</p>
                <a href="#" class="project-link">View Project</a>
            </div>
            <div class="project-card">
                <h3>Project Two</h3>
                <p>A description of your second project goes here.</p>
                <a href="#" class="project-link">View Project</a>
            </div>
            <div class="project-card">
                <h3>Project Three</h3>
                <p>A description of your third project goes here.</p>
                <a href="#" class="project-link">View Project</a>
            </div>
        </div>
    </section>

    <footer id="contact">
        <h2>Get In Touch</h2>
        <p>Feel free to reach out!</p>
        <div class="contact-links">
            <a href="mailto:your-email@example.com">Email</a>
            <a href="https://github.com/your-username">GitHub</a>
        </div>
    </footer>

    <script src="script.js"></script>
</body>
</html>

What does this code do?

  • The <nav> block creates a fixed navigation bar with links to each section of the page.
  • The <header id="hero"> is the full-screen landing area with your name and a tagline.
  • The <section> elements define the About and Projects content areas. Each has a unique id so the nav links can scroll to them.
  • The <footer> holds your contact links and email.
  • The <script> tag at the bottom loads your JavaScript file after the page content is ready.
  • Save index.html by pressing Cmd+S (macOS) or Ctrl+S (Windows).
  • Open the file in your browser by right-clicking index.html in the VS Code Explorer and selecting Reveal in Finder (macOS) or Reveal in File Explorer (Windows), then double-clicking the file.
  • You should see unstyled text: a list of navigation links, your name, project titles, and contact links. Everything will look plain because there is no CSS yet.

Seeing a blank page?

Check that you saved the file (look for the dot on the file tab in VS Code, which means unsaved changes). Also confirm you opened index.html in the browser, not style.css or script.js.

help me see my HTML in the browser

✔️ Awesome, I've got everything!

Great. Double check you've saved index.html and move on to the next substep.

ⓧ I'd like to double check the full code

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>My Portfolio</title>
    <link rel="stylesheet" href="style.css">
</head>
<body>
    <nav id="navbar">
        <div class="nav-container">
            <a href="#" class="logo">Portfolio</a>
            <ul class="nav-links">
                <li><a href="#about">About</a></li>
                <li><a href="#projects">Projects</a></li>
                <li><a href="#contact">Contact</a></li>
            </ul>
        </div>
    </nav>

    <header id="hero">
        <h1>Hi, I'm <span class="highlight">Your Name</span></h1>
        <p>A developer learning to build and deploy web applications.</p>
    </header>

    <section id="about">
        <h2>About Me</h2>
        <p>I'm a developer passionate about building things for the web. I'm currently learning modern deployment workflows and cloud hosting with Vercel.</p>
    </section>

    <section id="projects">
        <h2>My Projects</h2>
        <div class="project-grid">
            <div class="project-card">
                <h3>Project One</h3>
                <p>A description of your first project goes here.</p>
                <a href="#" class="project-link">View Project</a>
            </div>
            <div class="project-card">
                <h3>Project Two</h3>
                <p>A description of your second project goes here.</p>
                <a href="#" class="project-link">View Project</a>
            </div>
            <div class="project-card">
                <h3>Project Three</h3>
                <p>A description of your third project goes here.</p>
                <a href="#" class="project-link">View Project</a>
            </div>
        </div>
    </section>

    <footer id="contact">
        <h2>Get In Touch</h2>
        <p>Feel free to reach out!</p>
        <div class="contact-links">
            <a href="mailto:your-email@example.com">Email</a>
            <a href="https://github.com/your-username">GitHub</a>
        </div>
    </footer>

    <script src="script.js"></script>
</body>
</html>
Style with CSS and add interactivity

The raw HTML looks bare. CSS transforms it into a polished, responsive portfolio with a gradient hero section, a fixed navigation bar, and hover effects on your project cards.

  • In VS Code, click on style.css in the Explorer sidebar to open it.
  • Paste the following CSS to set up the base reset, navigation, and hero section:
* {
    margin: 0;
    padding: 0;
    box-sizing: border-box;
}

body {
    font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
    line-height: 1.6;
    color: #333;
}

/* Navigation */
#navbar {
    position: fixed;
    top: 0;
    width: 100%;
    background: #fff;
    box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);
    z-index: 1000;
}

.nav-container {
    max-width: 1100px;
    margin: 0 auto;
    padding: 1rem 2rem;
    display: flex;
    justify-content: space-between;
    align-items: center;
}

.logo {
    font-size: 1.5rem;
    font-weight: bold;
    color: #333;
    text-decoration: none;
}

.nav-links {
    display: flex;
    list-style: none;
    gap: 2rem;
}

.nav-links a {
    text-decoration: none;
    color: #555;
    transition: color 0.3s;
}

.nav-links a:hover {
    color: #0070f3;
}

/* Hero */
#hero {
    min-height: 100vh;
    display: flex;
    flex-direction: column;
    justify-content: center;
    align-items: center;
    text-align: center;
    padding: 2rem;
    background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
    color: #fff;
}

#hero h1 {
    font-size: 3rem;
    margin-bottom: 1rem;
}

#hero p {
    font-size: 1.25rem;
    opacity: 0.9;
}

.highlight {
    color: #ffd700;
}

What does this code do?

  • The * reset removes default browser margins and padding so your layout starts from a clean slate.
  • The #navbar styles fix the navigation bar to the top of the viewport. It stays visible as visitors scroll.
  • The #hero section uses a purple gradient background and centers the content vertically. It takes up the full viewport height.
  • Save style.css and refresh your browser.
  • You should see a purple-to-violet gradient filling the screen, with your name in gold text and the navigation bar pinned to the top.

Still seeing unstyled text?

Confirm that your index.html file has <link rel="stylesheet" href="style.css"> in the <head> section. Also check that style.css is in the same folder as index.html.

help me fix my CSS not loading

  • Now add the remaining CSS below the code you just pasted in style.css. This styles the content sections, project cards, footer, and responsive layout:
/* Sections */
section {
    max-width: 1100px;
    margin: 0 auto;
    padding: 5rem 2rem;
}

section h2 {
    font-size: 2rem;
    margin-bottom: 1.5rem;
    color: #333;
}

/* Projects */
.project-grid {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
    gap: 2rem;
    margin-top: 2rem;
}

.project-card {
    background: #f8f9fa;
    border-radius: 8px;
    padding: 2rem;
    transition: transform 0.3s, box-shadow 0.3s;
}

.project-card:hover {
    transform: translateY(-5px);
    box-shadow: 0 10px 30px rgba(0, 0, 0, 0.1);
}

.project-card h3 {
    margin-bottom: 0.75rem;
    color: #333;
}

.project-card p {
    margin-bottom: 1rem;
    color: #666;
}

.project-link {
    color: #0070f3;
    text-decoration: none;
    font-weight: 500;
}

.project-link:hover {
    text-decoration: underline;
}

/* Footer */
footer {
    background: #1a1a2e;
    color: #fff;
    text-align: center;
    padding: 5rem 2rem;
}

footer h2 {
    color: #fff;
}

footer p {
    margin: 1rem 0;
    opacity: 0.8;
}

.contact-links {
    display: flex;
    justify-content: center;
    gap: 2rem;
    margin-top: 1.5rem;
}

.contact-links a {
    color: #0070f3;
    text-decoration: none;
    font-size: 1.1rem;
}

.contact-links a:hover {
    text-decoration: underline;
}

/* Responsive */
@media (max-width: 768px) {
    #hero h1 {
        font-size: 2rem;
    }

    .nav-links {
        gap: 1rem;
    }

    .project-grid {
        grid-template-columns: 1fr;
    }
}

What does this code do?

  • The .project-grid uses CSS Grid to automatically arrange project cards in a responsive layout. Cards stack on smaller screens.
  • The .project-card:hover rules lift the card upward and add a shadow when a visitor hovers over it.
  • The @media block adjusts font sizes and grid layout for screens narrower than 768px, keeping the site readable on phones.
  • Save style.css and refresh your browser.
  • Scroll past the hero. You should see three project cards in a grid, each with a light gray background. The footer at the bottom has a dark navy background with blue contact links.

Project cards not showing in a grid?

Make sure you pasted the second CSS block below (not replacing) the first block. Both blocks need to be in style.css together. If the cards are stacking vertically, your browser window might be narrower than 300px per card. Try widening the window.

help me fix my project cards layout

✔️ Awesome, I've got everything!

Great. Double check you've saved style.css and move on to the JavaScript.

ⓧ I'd like to double check the full code

* {
    margin: 0;
    padding: 0;
    box-sizing: border-box;
}

body {
    font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
    line-height: 1.6;
    color: #333;
}

/* Navigation */
#navbar {
    position: fixed;
    top: 0;
    width: 100%;
    background: #fff;
    box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);
    z-index: 1000;
}

.nav-container {
    max-width: 1100px;
    margin: 0 auto;
    padding: 1rem 2rem;
    display: flex;
    justify-content: space-between;
    align-items: center;
}

.logo {
    font-size: 1.5rem;
    font-weight: bold;
    color: #333;
    text-decoration: none;
}

.nav-links {
    display: flex;
    list-style: none;
    gap: 2rem;
}

.nav-links a {
    text-decoration: none;
    color: #555;
    transition: color 0.3s;
}

.nav-links a:hover {
    color: #0070f3;
}

/* Hero */
#hero {
    min-height: 100vh;
    display: flex;
    flex-direction: column;
    justify-content: center;
    align-items: center;
    text-align: center;
    padding: 2rem;
    background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
    color: #fff;
}

#hero h1 {
    font-size: 3rem;
    margin-bottom: 1rem;
}

#hero p {
    font-size: 1.25rem;
    opacity: 0.9;
}

.highlight {
    color: #ffd700;
}

/* Sections */
section {
    max-width: 1100px;
    margin: 0 auto;
    padding: 5rem 2rem;
}

section h2 {
    font-size: 2rem;
    margin-bottom: 1.5rem;
    color: #333;
}

/* Projects */
.project-grid {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
    gap: 2rem;
    margin-top: 2rem;
}

.project-card {
    background: #f8f9fa;
    border-radius: 8px;
    padding: 2rem;
    transition: transform 0.3s, box-shadow 0.3s;
}

.project-card:hover {
    transform: translateY(-5px);
    box-shadow: 0 10px 30px rgba(0, 0, 0, 0.1);
}

.project-card h3 {
    margin-bottom: 0.75rem;
    color: #333;
}

.project-card p {
    margin-bottom: 1rem;
    color: #666;
}

.project-link {
    color: #0070f3;
    text-decoration: none;
    font-weight: 500;
}

.project-link:hover {
    text-decoration: underline;
}

/* Footer */
footer {
    background: #1a1a2e;
    color: #fff;
    text-align: center;
    padding: 5rem 2rem;
}

footer h2 {
    color: #fff;
}

footer p {
    margin: 1rem 0;
    opacity: 0.8;
}

.contact-links {
    display: flex;
    justify-content: center;
    gap: 2rem;
    margin-top: 1.5rem;
}

.contact-links a {
    color: #0070f3;
    text-decoration: none;
    font-size: 1.1rem;
}

.contact-links a:hover {
    text-decoration: underline;
}

/* Responsive */
@media (max-width: 768px) {
    #hero h1 {
        font-size: 2rem;
    }

    .nav-links {
        gap: 1rem;
    }

    .project-grid {
        grid-template-columns: 1fr;
    }
}
  • Now open script.js in VS Code and paste the following JavaScript to add smooth scrolling and a navbar scroll effect:
// Smooth scroll for navigation links
document.querySelectorAll('a[href^="#"]').forEach(anchor => {
    anchor.addEventListener('click', function (e) {
        e.preventDefault();
        const target = document.querySelector(this.getAttribute('href'));
        if (target) {
            target.scrollIntoView({
                behavior: 'smooth',
                block: 'start'
            });
        }
    });
});

// Add scroll effect to navbar
window.addEventListener('scroll', () => {
    const navbar = document.getElementById('navbar');
    if (window.scrollY > 50) {
        navbar.style.background = 'rgba(255, 255, 255, 0.95)';
    } else {
        navbar.style.background = '#fff';
    }
});

What does this code do?

  • The first block selects every anchor link that starts with # and overrides the default jump behavior. Instead, it smoothly scrolls the page to the target section.
  • The second block listens for scroll events. When the visitor scrolls past 50 pixels, the navbar background becomes slightly transparent, creating a subtle glass effect.
  • Save script.js and refresh your browser.
  • Click the About link in the navigation bar. The page should smoothly scroll down to the About Me section instead of jumping instantly.

Smooth scroll not working?

Check that script.js is saved and that the <script src="script.js"></script> tag is present at the bottom of your index.html file, just before </body>. Open your browser's developer console (Cmd+Option+J on macOS or Ctrl+Shift+J on Windows) and look for any red error messages.

help me debug my JavaScript

✔️ Awesome, I've got everything!

Great. Double check you've saved all three files and move on.

ⓧ I'd like to double check the full code

// Smooth scroll for navigation links
document.querySelectorAll('a[href^="#"]').forEach(anchor => {
    anchor.addEventListener('click', function (e) {
        e.preventDefault();
        const target = document.querySelector(this.getAttribute('href'));
        if (target) {
            target.scrollIntoView({
                behavior: 'smooth',
                block: 'start'
            });
        }
    });
});

// Add scroll effect to navbar
window.addEventListener('scroll', () => {
    const navbar = document.getElementById('navbar');
    if (window.scrollY > 50) {
        navbar.style.background = 'rgba(255, 255, 255, 0.95)';
    } else {
        navbar.style.background = '#fff';
    }
});

Your portfolio is looking sharp. Next up, you'll push this code to GitHub so Vercel can access it for deployment.

Push to GitHub

Your portfolio site is built and looking great locally. Now you need to get it online so Vercel can access and deploy it.

GitHub is a cloud platform that stores your code in repositories. By pushing your portfolio to GitHub, you create a connection that Vercel can use to automatically deploy your site every time you make a change.

In this step, get ready to:
  • Initialize a Git repository and make your first commit.
  • Create a GitHub repository and push your code to it.
Initialize a Git repository

Before you can push code to GitHub, you need to turn your project folder into a Git repository. Git tracks every change you make to your files, creating a history you can reference and revert to.

You also need a .gitignore file to tell Git which files to skip. Some files are auto-generated or contain local settings that should not be shared.

  • Open a terminal inside your my-portfolio folder by pressing Ctrl+` in VS Code (this opens the integrated terminal).
  • Create your .gitignore file by running this command:
printf '.vercel\nnode_modules\n.DS_Store\n' > .gitignore

What does this command do?

The printf command writes three lines into a new .gitignore file.

  • .vercel ignores Vercel's local project settings (created later when you use the CLI).
  • node_modules ignores installed packages (not needed in this project, but good practice for any web project).
  • .DS_Store ignores macOS system files that clutter repositories.
  • Confirm the file was created by running:
ls -a

You should see .gitignore listed alongside index.html, style.css, and script.js.

Don't see .gitignore in the list?

Files starting with a dot are hidden by default. The -a flag in ls -a reveals hidden files. If you still don't see it, make sure you ran the printf command from inside the my-portfolio folder.

help me find my .gitignore file

✔️ Awesome, I've got everything!

Great. Make sure you saved any open files before moving on.

ⓧ I'd like to double check the full code

Here is the complete .gitignore file:

.vercel
node_modules
.DS_Store

Now that .gitignore is in place, you can initialize Git and lock in your first commit.

  • Initialize a Git repository and commit all your files by running these commands:
git init
git add .
git commit -m "Initial commit"

What do these commands do?

  • git init creates a new Git repository in your current folder. This adds a hidden .git directory that stores your project's version history.
  • git add . stages all files in the folder (except those listed in .gitignore) for your next commit.
  • git commit -m "Initial commit" saves a snapshot of your staged files with a message describing the change.

You should see output listing the files that were committed, including index.html, style.css, script.js, and .gitignore.

Seeing 'please tell me who you are'?

Git needs your name and email to label commits. Run these two commands (replacing the placeholder values with your own info), then re-run the commit command:

git config --global user.email "your-email@example.com"
git config --global user.name "Your Name"

help me configure Git for the first time

Create a GitHub repository and push

Your code is committed locally, but it needs to live on GitHub so Vercel can access it. You will create an empty repository on GitHub and then push your local commits to it.

  • Open your browser and go to github.com/new.
  • Type my-portfolio in the Repository name field.
  • Select Public for the repository visibility.
  • Leave all other options unchecked (do not add a README, .gitignore, or license).
  • Click Create repository.

Why leave the repository empty?

You already have files and a commit locally. If you let GitHub add a README or .gitignore, it creates a separate commit that conflicts with your local history. Starting empty avoids merge headaches.

After creating the repository, GitHub shows a page with setup instructions. You will use the commands under the heading that says "push an existing repository from the command line."

  • Back in your terminal, connect your local repository to GitHub and push your code by running these commands:
git remote add origin [[YOUR_REPO_URL="https://github.com/your-username/my-portfolio.git"]]
git branch -M main
git push -u origin main

What do these commands do?

  • git remote add origin tells your local repository where the GitHub repository lives. The URL is the address you copy from the GitHub setup page.
  • git branch -M main renames your current branch to main (the standard branch name GitHub expects).
  • git push -u origin main uploads your commits to GitHub. The -u flag sets origin main as the default push destination so future pushes only need git push.
  • Refresh your GitHub repository page in the browser.

You should see your four files listed: index.html, style.css, script.js, and .gitignore.

Push failed or asked for a password?

GitHub no longer accepts password authentication for Git operations. If you see an authentication error, you need to set up a personal access token or SSH key.

  • Go to github.com/settings/tokens and generate a new token with repo scope.
  • When Git asks for your password, paste the token instead of your account password.

help me authenticate Git with GitHub

Your code is now safely stored on GitHub and ready for Vercel to pick up. Next, you will connect this repository to Vercel and watch your portfolio go live on the internet.

Deploy via the Vercel Dashboard

Your code is on GitHub, but it's not accessible to anyone as a website yet.

In this step, you'll deploy your portfolio to Vercel using its web dashboard. Vercel connects directly to your GitHub repo, builds your site, and distributes it across a global CDN so anyone in the world can visit it.

In this step, get ready to:
  • Import your GitHub repository into Vercel.
  • Deploy your site and visit it live on a .vercel.app URL.
  • Explore the deployment dashboard to understand build logs and domains.
Import your GitHub repository

Vercel's dashboard lets you connect a GitHub repo and deploy it in a few clicks. Once connected, every future push to your main branch triggers an automatic redeployment.

  • Open your browser and go to vercel.com/new.
  • You'll see an Import Git Repository section with a list of your GitHub repositories.
  • Find my-portfolio in the list and click the Import button next to it.

Vercel takes you to a configuration page where you can customize settings before deploying.

  • Look at the Framework Preset dropdown. Vercel auto-detects your project as a static site and sets this to Other.
  • Leave all other settings at their defaults. No build command or output directory is needed for a plain HTML/CSS/JS site.

Why is the Framework Preset set to Other?

Vercel supports many frameworks like Next.js, Vite, and Nuxt. Since your project uses plain HTML, CSS, and JavaScript with no build step, Vercel classifies it as Other and serves your files directly without running a build command.

Don't see your repository in the list?

Make sure you signed up for Vercel using your GitHub account. If you used a different method, you may need to connect your GitHub account in your Vercel settings. Also confirm that your my-portfolio repo is pushed to GitHub and not set to private under an organization you haven't granted Vercel access to.

Help me connect my GitHub account to Vercel.

Deploy and verify your live site

With the configuration confirmed, you're ready to deploy. This sends your code to Vercel's servers where it becomes a live website.

  • Click the Deploy button at the bottom of the configuration page.
  • Wait for the build to complete. You'll see a progress screen with build logs scrolling in real time.
  • Once the build finishes, Vercel shows a congratulations screen with a preview of your deployed site.
  • Click the preview image or the generated .vercel.app URL to open your live portfolio in a new tab.

You should see your full portfolio site live on the internet. The gradient hero section, navigation bar, project cards, and footer are all visible and functional.

What is a .vercel.app URL?

Vercel assigns every project a free subdomain under .vercel.app. This is your production URL that anyone can visit. You can add a custom domain later, but the .vercel.app URL always stays active as a fallback.

Site looks broken or shows a blank page?

Check that your index.html file is in the root of your repository, not inside a subfolder. Vercel serves files from the root directory by default. Also confirm your style.css and script.js file references in index.html use relative paths (no leading / or ./ prefix needed).

Help me debug why my Vercel deployment shows a blank page.

Explore the deployment dashboard

Now that your site is deployed, the Vercel dashboard gives you a full overview of the deployment. This is where you'll monitor builds and manage your project going forward.

  • Return to your Vercel dashboard by clicking Continue to Dashboard on the congratulations screen (or navigate to vercel.com and click your project).
  • On the project overview page, notice the Production section showing your live deployment with its URL and the Git commit that triggered it.
  • Click on the deployment entry to view its details.
  • Explore the Building section to see your build logs. For a static site, these are minimal since there's no build command to run.
  • Note the Domains section showing both your production domain (.vercel.app URL) and the unique deployment URL assigned to this specific build.

What is the difference between a deployment URL and a production URL?

Every deployment gets its own unique URL that never changes. This is like a snapshot of your site at that exact commit. The production URL (your .vercel.app domain) always points to your latest production deployment.

When you push a new commit to main, Vercel creates a new deployment and updates the production URL to point to it. The old deployment's unique URL still works if you ever need to revisit it.

Your portfolio is officially live on the internet. Next up, you'll learn the developer-focused way to deploy using the Vercel CLI directly from your terminal.

Deploy via the Vercel CLI

Your portfolio is already live thanks to the Dashboard deployment in the previous step. Now you will learn the second deployment method that professional developers use daily: the Vercel CLI.

The CLI lets you deploy directly from your terminal without opening a browser. This is the workflow most developers prefer for quick iterations and scripting deployments.

In this step, get ready to:
  • Install the Vercel CLI globally and verify the installation.
  • Authenticate and deploy a preview from your terminal.
  • Promote a deployment to production using the CLI.
Install the Vercel CLI

The Vercel CLI is an npm package you install globally so you can run deployment commands from any directory on your machine.

  • Return to the terminal you used earlier.
  • Install the Vercel CLI globally by running this command:
npm i -g vercel

What does this command do?

The -g flag installs the package globally, meaning you can run vercel from any folder on your computer. Without it, the CLI would only be available inside one specific project.

  • Verify the installation succeeded by running:
vercel --version

You should see a version number printed in your terminal (e.g. Vercel CLI 53.3.2).

Seeing command not found?

If your terminal does not recognize vercel, the global npm bin directory is not in your PATH. Try closing and reopening your terminal, then run vercel --version again.

If it still does not work, you can prefix any command in this step with npx (e.g. npx vercel --version) as an alternative.

Help me fix vercel command not found after installing with npm.

Log in and deploy a preview

Before the CLI can deploy your project, it needs to know who you are. The login command opens your browser so you can authenticate with your Vercel account.

  • Start the authentication flow by running:
vercel login

What happens when you run this?

The CLI opens a browser tab where you confirm the login. After you approve it in the browser, your terminal displays a success message confirming authentication is complete.

  • Approve the login in the browser tab that opens, then return to your terminal.
  • Make sure you are inside the my-portfolio folder by running:
cd ~/Desktop/my-portfolio
  • Deploy a preview of your site by running:
vercel

What prompts will I see?

Since this is the first time running vercel in this directory, the CLI asks a few setup questions to link your local folder to your existing Vercel project.

  • "Set up and deploy?" - Enter Y.
  • "Which scope do you want to deploy to?" - Select your personal account.
  • "Link to existing project?" - Enter Y.
  • "What's the name of your existing project?" - Enter my-portfolio.

After answering, the CLI links your folder (creating a .vercel directory) and immediately deploys. It outputs a unique preview URL when finished.

  • Copy the preview URL the CLI outputs and open it in your browser.

You should see your portfolio site loaded at this new preview URL.

Deploy failed or project not found?

Make sure you typed my-portfolio as the project name exactly as it appears in your Vercel Dashboard. The name is case-sensitive.

If the CLI asks if you want to link to an existing project and you accidentally selected No, it tries to create a new project. Press Ctrl+C to cancel and run vercel again.

Help me link my local folder to my existing Vercel project via the CLI.

Deploy to production

The vercel command creates a preview deployment by default. To push your site to the live production URL, you add the --prod flag.

  • Deploy to production by running:
vercel --prod

What does --prod do?

Without the flag, vercel creates a preview deployment at a unique URL meant for testing. With --prod, the CLI deploys to your production domain. That is the same .vercel.app URL you saw when you deployed from the Dashboard.

  • Visit your production .vercel.app URL in your browser.

You should see your portfolio site at the same production URL, now deployed via the CLI.

Production URL not updating?

Give it a few seconds. Vercel's CDN propagates changes globally, which can take a moment. Try a hard refresh with Cmd+Shift+R (macOS) or Ctrl+Shift+R (Windows).

My Vercel production deployment is not showing the latest changes after running vercel --prod.

Why use the CLI when the Dashboard works?

The Dashboard is great for connecting a repo once and letting automatic deploys handle everything. The CLI is the go-to when you want to deploy without pushing to Git, test a quick change in a preview environment, or script deployments in a CI/CD pipeline.

Most professional teams use both: automatic Git-based deploys for the main workflow, and the CLI for fast manual deploys and debugging.

You have now deployed your portfolio two different ways. Next up, you will trigger an automatic deployment by pushing a new feature branch to GitHub.

Trigger an Automatic Deployment

You've now deployed your portfolio using both the Vercel Dashboard and the CLI. Those are great for manual control, but the real power of Vercel is what happens automatically.

In professional teams, developers don't manually deploy every change. They push code to a branch, get a preview URL to review, then merge into main to go live. In this step, you'll experience that full workflow firsthand.

In this step, get ready to:
  • Create a feature branch and add a new skills section to your portfolio.
  • Push the branch and watch Vercel automatically create a preview deployment.
  • Merge to production and confirm your live site updates automatically.
Create a feature branch

Feature branches let you develop new functionality without affecting your live site. Vercel treats any branch that isn't main as a preview branch and automatically deploys it to a unique URL.

  • Switch back to your terminal in the my-portfolio folder.
  • Create and switch to a new branch by running this command:
git checkout -b add-skills-section

What does this command do?

The -b flag tells Git to create a new branch called add-skills-section and immediately switch to it. Any changes you make now live on this branch, separate from main.

Now you'll add a skills section to your portfolio. This requires two changes: a new nav link and the section itself.

  • Open index.html in your code editor.
  • Find the nav links list. Locate this line:
<li><a href="#projects">Projects</a></li>
  • Add a new skills link directly below it, so the nav links look like this:
<li><a href="#projects">Projects</a></li>
<li><a href="#skills">Skills</a></li>
<li><a href="#contact">Contact</a></li>
  • Now scroll down to find the closing </section> tag of the projects section (after the last project-card div). Add the following skills section between the projects section and the footer:
    <section id="skills">
        <h2>Skills</h2>
        <div class="skills-list">
            <span class="skill-tag">HTML</span>
            <span class="skill-tag">CSS</span>
            <span class="skill-tag">JavaScript</span>
            <span class="skill-tag">Git</span>
            <span class="skill-tag">Vercel</span>
        </div>
    </section>

What does this code do?

This adds a new page section with five skill tags. Each tag is a <span> element with the class skill-tag, which you'll style next. The skills-list wrapper uses flexbox to arrange them in a row.

The nav link you added lets visitors scroll directly to this section using the smooth scroll you built earlier.

  • Save index.html.

Next, add the CSS to style the skill tags.

  • Open style.css in your code editor.
  • Find the /* Footer */ comment. Add the following styles directly above it:
/* Skills */
.skills-list {
    display: flex;
    flex-wrap: wrap;
    gap: 1rem;
    margin-top: 1rem;
}

.skill-tag {
    background: #0070f3;
    color: #fff;
    padding: 0.5rem 1rem;
    border-radius: 20px;
    font-size: 0.9rem;
    font-weight: 500;
}

What does this code do?

The .skills-list class uses flexbox with flex-wrap: wrap so skill tags flow onto a new line if they run out of space.

Each .skill-tag gets a blue pill-shaped appearance with border-radius: 20px and white text on a Vercel-blue background.

  • Save style.css.
  • Open index.html directly in your browser (double-click the file in Finder or File Explorer). You should see a new Skills heading below your projects with five blue pill-shaped tags.

Don't see the skill tags styled?

  • Make sure you saved both index.html and style.css.
  • Check that the /* Skills */ block is placed above the /* Footer */ comment, not inside another rule.
  • Confirm the class names in your CSS (.skills-list and .skill-tag) match the ones in your HTML exactly.

Ask for help: Why aren't my skill tags showing up with styles?

✔️ Awesome, I've got everything!

Great. Make sure both files are saved before moving on.

ⓧ I'd like to double check the full code

Here is the full index.html at this point:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>My Portfolio</title>
    <link rel="stylesheet" href="style.css">
</head>
<body>
    <nav id="navbar">
        <div class="nav-container">
            <a href="#" class="logo">Portfolio</a>
            <ul class="nav-links">
                <li><a href="#about">About</a></li>
                <li><a href="#projects">Projects</a></li>
                <li><a href="#skills">Skills</a></li>
                <li><a href="#contact">Contact</a></li>
            </ul>
        </div>
    </nav>

    <header id="hero">
        <h1>Hi, I'm <span class="highlight">Your Name</span></h1>
        <p>A developer learning to build and deploy web applications.</p>
    </header>

    <section id="about">
        <h2>About Me</h2>
        <p>I'm a developer passionate about building things for the web. I'm currently learning modern deployment workflows and cloud hosting with Vercel.</p>
    </section>

    <section id="projects">
        <h2>My Projects</h2>
        <div class="project-grid">
            <div class="project-card">
                <h3>Project One</h3>
                <p>A description of your first project goes here.</p>
                <a href="#" class="project-link">View Project</a>
            </div>
            <div class="project-card">
                <h3>Project Two</h3>
                <p>A description of your second project goes here.</p>
                <a href="#" class="project-link">View Project</a>
            </div>
            <div class="project-card">
                <h3>Project Three</h3>
                <p>A description of your third project goes here.</p>
                <a href="#" class="project-link">View Project</a>
            </div>
        </div>
    </section>

    <section id="skills">
        <h2>Skills</h2>
        <div class="skills-list">
            <span class="skill-tag">HTML</span>
            <span class="skill-tag">CSS</span>
            <span class="skill-tag">JavaScript</span>
            <span class="skill-tag">Git</span>
            <span class="skill-tag">Vercel</span>
        </div>
    </section>

    <footer id="contact">
        <h2>Get In Touch</h2>
        <p>Feel free to reach out!</p>
        <div class="contact-links">
            <a href="mailto:your-email@example.com">Email</a>
            <a href="https://github.com/your-username">GitHub</a>
        </div>
    </footer>

    <script src="script.js"></script>
</body>
</html>

And here is the full style.css:

* {
    margin: 0;
    padding: 0;
    box-sizing: border-box;
}

body {
    font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
    line-height: 1.6;
    color: #333;
}

/* Navigation */
#navbar {
    position: fixed;
    top: 0;
    width: 100%;
    background: #fff;
    box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);
    z-index: 1000;
}

.nav-container {
    max-width: 1100px;
    margin: 0 auto;
    padding: 1rem 2rem;
    display: flex;
    justify-content: space-between;
    align-items: center;
}

.logo {
    font-size: 1.5rem;
    font-weight: bold;
    color: #333;
    text-decoration: none;
}

.nav-links {
    display: flex;
    list-style: none;
    gap: 2rem;
}

.nav-links a {
    text-decoration: none;
    color: #555;
    transition: color 0.3s;
}

.nav-links a:hover {
    color: #0070f3;
}

/* Hero */
#hero {
    min-height: 100vh;
    display: flex;
    flex-direction: column;
    justify-content: center;
    align-items: center;
    text-align: center;
    padding: 2rem;
    background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
    color: #fff;
}

#hero h1 {
    font-size: 3rem;
    margin-bottom: 1rem;
}

#hero p {
    font-size: 1.25rem;
    opacity: 0.9;
}

.highlight {
    color: #ffd700;
}

/* Sections */
section {
    max-width: 1100px;
    margin: 0 auto;
    padding: 5rem 2rem;
}

section h2 {
    font-size: 2rem;
    margin-bottom: 1.5rem;
    color: #333;
}

/* Projects */
.project-grid {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
    gap: 2rem;
    margin-top: 2rem;
}

.project-card {
    background: #f8f9fa;
    border-radius: 8px;
    padding: 2rem;
    transition: transform 0.3s, box-shadow 0.3s;
}

.project-card:hover {
    transform: translateY(-5px);
    box-shadow: 0 10px 30px rgba(0, 0, 0, 0.1);
}

.project-card h3 {
    margin-bottom: 0.75rem;
    color: #333;
}

.project-card p {
    margin-bottom: 1rem;
    color: #666;
}

.project-link {
    color: #0070f3;
    text-decoration: none;
    font-weight: 500;
}

.project-link:hover {
    text-decoration: underline;
}

/* Skills */
.skills-list {
    display: flex;
    flex-wrap: wrap;
    gap: 1rem;
    margin-top: 1rem;
}

.skill-tag {
    background: #0070f3;
    color: #fff;
    padding: 0.5rem 1rem;
    border-radius: 20px;
    font-size: 0.9rem;
    font-weight: 500;
}

/* Footer */
footer {
    background: #1a1a2e;
    color: #fff;
    text-align: center;
    padding: 5rem 2rem;
}

footer h2 {
    color: #fff;
}

footer p {
    margin: 1rem 0;
    opacity: 0.8;
}

.contact-links {
    display: flex;
    justify-content: center;
    gap: 2rem;
    margin-top: 1.5rem;
}

.contact-links a {
    color: #0070f3;
    text-decoration: none;
    font-size: 1.1rem;
}

.contact-links a:hover {
    text-decoration: underline;
}

/* Responsive */
@media (max-width: 768px) {
    #hero h1 {
        font-size: 2rem;
    }

    .nav-links {
        gap: 1rem;
    }

    .project-grid {
        grid-template-columns: 1fr;
    }
}
Push and watch the preview deploy

Your code changes are ready on the add-skills-section branch. When you push this branch to GitHub, Vercel automatically detects it and creates a preview deployment with its own unique URL.

  • Stage and commit your changes by running these commands:
git add .
git commit -m "Add skills section"
  • Push the branch to GitHub by running this command:
git push origin add-skills-section

What happens when you push?

Because your my-portfolio repo is connected to Vercel, every push to a non-production branch automatically triggers a preview deployment. Vercel builds your site and gives it a unique URL so you can review changes before they go live.

This is the same workflow professional teams use to review code before merging. Each branch gets its own live preview.

  • Open your Vercel dashboard at vercel.com.
  • Click on your my-portfolio project.
  • Click Deployments in the sidebar.
  • You should see a new deployment listed with the branch name add-skills-section and a status of Ready. This is your preview deployment.
  • Click the preview URL next to the deployment to open it. You should see your portfolio with the new skills section visible.

Don't see the deployment?

  • Preview deployments take a few seconds to build. Wait 15-30 seconds and refresh the page.
  • Confirm your push succeeded by checking the terminal output. You should see a message indicating the branch was pushed to origin.
  • Make sure the GitHub repo is still connected to your Vercel project. Check Settings > Git in your Vercel project dashboard.

Ask for help: My branch push didn't trigger a Vercel preview deployment. How do I fix this?

Merge to production

Your preview looks good. Now you'll merge the branch into main to trigger a production deployment. Vercel creates a new production deployment every time it detects a push to the production branch.

  • Switch back to your main branch and merge by running these commands:
git checkout main
git merge add-skills-section
git push origin main

What does merging do?

The git merge command brings all the changes from add-skills-section into your main branch. When you push main to GitHub, Vercel detects the update and automatically creates a new production deployment.

This is continuous deployment in action. Your live site updates without you clicking a single deploy button.

  • Return to your Vercel dashboard and click Deployments in the sidebar.
  • You should see a new production deployment building or completed for the main branch.
  • Visit your production .vercel.app URL. You should see your portfolio with the new skills section and blue skill tags now live on the internet.

Production site not updating?

  • Give it 15-30 seconds for the build to complete. Refresh your Vercel dashboard to see the status change to Ready.
  • Make sure you pushed to main (not just merged locally). Run git log --oneline -3 to confirm the merge commit is in your history, then check that the push completed.
  • Hard-refresh your browser with Cmd+Shift+R (macOS) or Ctrl+Shift+R (Windows) to bypass any cached version.

Ask for help: My production deployment didn't update after merging to main. What should I check?

You've just completed the full Git-based deployment workflow that professional teams use every day. Branch, build, preview, merge, ship. Your portfolio is now live with its new skills section, and you didn't touch a single deploy button to make it happen.

Secret mission

Custom 404 Page and Redirects

Your portfolio is live, but what happens when someone visits a page that doesn't exist? Right now they see a generic error page. In this secret mission, you'll add a polished custom 404 page and configure URL redirects using Vercel's vercel.json configuration file.

Clean Up Your Resources

Clean Up Your Resources

This project runs entirely on the Vercel Hobby plan, which is free with no billing cycles. There are no ongoing costs for keeping your portfolio deployed.

Decide whether to keep your resources running, pause them to come back later, or delete them entirely.

Resources you used:

  • Local my-portfolio project folder on your Desktop.
  • GitHub repository my-portfolio.
  • Vercel project and production deployment.
  • Vercel CLI (installed globally via npm).

Keep everything running

No action needed. Choose this if you're still actively building on your portfolio or plan to customize it further.

  • Your site stays live at its .vercel.app URL for free.
  • Every push to main continues to trigger automatic production deployments.
  • The Vercel CLI remains available for future projects.

Pause - I'll come back to this later

Since Vercel's Hobby plan is free, your deployment costs nothing to keep running. There is nothing to pause from a billing perspective.

  • Your site remains live at no cost. Come back any time and push new changes.
  • If you want to stop automatic deploys temporarily, disconnect your GitHub repo from the Vercel project by opening your project on vercel.com, clicking Settings in the sidebar, selecting Git, and clicking Disconnect.

Delete - I don't want to use this again

Remove all project resources and start fresh. Follow each step below.

  • Delete the Vercel project:
  • Delete the GitHub repository:
  • Uninstall the Vercel CLI:
  • Delete the local project folder (macOS/Linux):
  • Delete the local project folder (Windows):
npm uninstall -g vercel
rm -rf ~/Desktop/my-portfolio
Remove-Item -Recurse -Force ~\Desktop\my-portfolio

Nice Work!

Nice Work!

Nice work! Your personal portfolio website is live on the internet, deployed to Vercel's global CDN with a fully automated deployment pipeline.

You've learned how to:

  • Built a responsive portfolio website from scratch with HTML, CSS, and JavaScript, and deployed it to Vercel's global CDN.
  • Deployed using both the Vercel Dashboard (GUI workflow) and the Vercel CLI (terminal workflow), understanding two professional deployment methods.
  • Set up continuous deployment where every push to main automatically updates your live site, and triggered preview deployments from a feature branch.
  • Configured a custom 404 page and URL redirects using a vercel.json configuration file.

Ready to quiz yourself?