Building a Simple Vue.js Interactive Portfolio Showcase: A Beginner’s Guide

Written by

in

In today’s digital landscape, a compelling online portfolio is essential for showcasing your skills and projects. Whether you’re a web developer, designer, writer, or any creative professional, a well-designed portfolio can be the key to landing your next opportunity. But building one from scratch can seem daunting, especially if you’re new to web development. This is where Vue.js comes in. Vue.js is a progressive JavaScript framework that makes it incredibly easy to build user interfaces. Its intuitive design and gentle learning curve make it perfect for beginners while still offering the power and flexibility needed for more complex projects.

Why Build a Portfolio with Vue.js?

There are several compelling reasons to choose Vue.js for your portfolio:

  • Ease of Learning: Vue.js is known for its approachable syntax and clear documentation, making it easier for beginners to grasp compared to other frameworks like React or Angular.
  • Component-Based Architecture: Vue.js encourages breaking down your UI into reusable components. This modular approach makes your code cleaner, more organized, and easier to maintain.
  • Performance: Vue.js is lightweight and optimized for performance, ensuring your portfolio loads quickly and provides a smooth user experience.
  • Flexibility: Vue.js is versatile and can be integrated into existing projects or used to build single-page applications (SPAs).
  • Community Support: A vibrant and active community provides ample resources, tutorials, and support to help you along the way.

Project Overview: Interactive Portfolio Showcase

In this tutorial, we’ll build a simple yet effective interactive portfolio showcase using Vue.js. This project will allow you to:

  • Display your projects with images, descriptions, and links.
  • Use a responsive layout that adapts to different screen sizes.
  • Implement a clean and intuitive user interface.

By the end of this tutorial, you’ll have a fully functional portfolio that you can customize to showcase your own work.

Prerequisites

Before we dive in, make sure you have the following:

  • Basic HTML, CSS, and JavaScript knowledge: While Vue.js simplifies many aspects of web development, understanding the fundamentals is crucial.
  • Node.js and npm (Node Package Manager) installed: These are essential for managing project dependencies and running the development server. You can download them from https://nodejs.org/.
  • A code editor: Choose your preferred editor (e.g., VS Code, Sublime Text, Atom).

Setting Up Your Vue.js Project

Let’s get started by setting up our Vue.js project using the Vue CLI (Command Line Interface). The Vue CLI simplifies project setup and provides a development server with hot-reloading.

  1. Install the Vue CLI: Open your terminal or command prompt and run the following command:
npm install -g @vue/cli
  1. Create a new project: Navigate to the directory where you want to create your project and run:
vue create my-portfolio

You’ll be prompted to choose a preset. Select the default preset (babel, eslint) for this tutorial. You can customize the configuration later if needed.

  1. Navigate to your project directory:
cd my-portfolio
  1. Run the development server:
npm run serve

This command starts the development server, and you should see your project running in your browser at http://localhost:8080/ (or a similar address). You’ll see a basic Vue.js welcome page.

Project Structure

Before we start coding, let’s understand the project structure created by the Vue CLI:

  • public/: This folder contains static assets like your index.html file, which is the entry point of your application.
  • src/: This is where you’ll spend most of your time.
    • components/: This folder will hold your reusable Vue components (e.g., ProjectCard.vue).
    • App.vue: The root component of your application.
    • main.js: The entry point of your JavaScript application, where you initialize Vue.
  • package.json: This file lists your project’s dependencies and scripts.

Building the Project Showcase Component

Now, let’s create the core component for displaying your projects. We’ll create a ProjectCard.vue component to represent each project.

  1. Create the ProjectCard.vue component: In the src/components/ directory, create a new file named ProjectCard.vue.
<template>
  <div class="project-card">
    <img :src="project.imageUrl" :alt="project.title" />
    <h3>{{ project.title }}</h3>
    <p>{{ project.description }}</p>
    <a :href="project.link" target="_blank">View Project</a>
  </div>
</template>

<script>
export default {
  props: {
    project: {
      type: Object,
      required: true,
    },
  },
};
</script>

<style scoped>
.project-card {
  border: 1px solid #ccc;
  padding: 20px;
  margin-bottom: 20px;
  border-radius: 5px;
  text-align: center;
}

img {
  max-width: 100%;
  height: auto;
  margin-bottom: 10px;
}

a {
  display: inline-block;
  padding: 10px 20px;
  background-color: #007bff;
  color: white;
  text-decoration: none;
  border-radius: 5px;
}
</style>

Let’s break down this code:

  • <template>: This section defines the HTML structure of the component. It uses a div with the class project-card to contain the project details.
  • <img>: Displays the project image. The :src attribute is a Vue.js directive that binds the src attribute to the project.imageUrl data. The :alt attribute provides alternative text for the image.
  • <h3>: Displays the project title using the {{ project.title }} syntax, which is Vue’s way of displaying data.
  • <p>: Displays the project description using {{ project.description }}.
  • <a>: Creates a link to the project. The :href attribute binds to the project.link data. The target="_blank" attribute opens the link in a new tab.
  • <script>: This section contains the JavaScript logic for the component.
    • props: {}: Defines the properties that the component accepts from its parent. In this case, it accepts a project object, which is required.
  • <style scoped>: This section contains the CSS styles specific to this component. The scoped attribute ensures that these styles only apply to this component and don’t affect other parts of your application.
  1. Import and use the ProjectCard component in App.vue: Open src/App.vue and modify it as follows:
<template>
  <div id="app">
    <h1>My Portfolio</h1>
    <div class="projects-container">
      <ProjectCard v-for="project in projects" :key="project.id" :project="project" />
    </div>
  </div>
</template>

<script>
import ProjectCard from './components/ProjectCard.vue';

export default {
  components: {
    ProjectCard,
  },
  data() {
    return {
      projects: [
        {
          id: 1,
          title: 'Project 1',
          description: 'This is a description of project 1.',
          imageUrl: 'https://via.placeholder.com/300x200', // Replace with your image URL
          link: '#',
        },
        {
          id: 2,
          title: 'Project 2',
          description: 'This is a description of project 2.',
          imageUrl: 'https://via.placeholder.com/300x200', // Replace with your image URL
          link: '#',
        },
        // Add more projects here
      ],
    };
  },
};
</script>

<style>
#app {
  font-family: Avenir, Helvetica, Arial, sans-serif;
  text-align: center;
  color: #2c3e50;
  margin-top: 60px;
}

.projects-container {
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  padding: 20px;
}
</style>

Here’s what changed in App.vue:

  • Import ProjectCard: import ProjectCard from './components/ProjectCard.vue'; imports the component we just created.
  • Register ProjectCard: components: { ProjectCard, }, registers the component so that Vue knows about it and can use it in the template.
  • Add project data: The data() function now includes an array of projects. Each project is an object with properties like id, title, description, imageUrl, and link. You’ll replace the placeholder image URLs with your own.
  • Use v-for to render multiple ProjectCards: The <ProjectCard v-for="project in projects" :key="project.id" :project="project" /> line uses Vue’s v-for directive to loop through the projects array and render a ProjectCard component for each project. The :key="project.id" provides a unique key for each item, which helps Vue efficiently update the DOM. The :project="project" passes the project data as a prop to the ProjectCard component.
  • Add basic styling: The <style> section includes some basic CSS to style the app and the project container.

At this point, you should see your projects displayed on the page. Remember to replace the placeholder image URLs with your actual image URLs.

Adding More Features

Now that we have the basic project showcase, let’s add some enhancements to make it more interactive and visually appealing.

1. Responsive Design

To make your portfolio responsive, you can use CSS media queries. This will allow your portfolio to adapt to different screen sizes, providing a better user experience on both desktop and mobile devices.

Modify the CSS in App.vue to include media queries. For example:

<style>
/* Existing styles */

.projects-container {
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  padding: 20px;
}

@media (max-width: 768px) {
  .projects-container {
    flex-direction: column;
    align-items: center;
  }
}
</style>

This media query changes the projects-container to a column layout on screens smaller than 768px, which is suitable for mobile devices. You can also adjust the styling of the project-card component for different screen sizes.

2. Project Filtering

If you have a large number of projects, adding filtering functionality can help users find what they’re looking for. Let’s add a simple filter based on project categories.

  1. Add category data to your project data: In App.vue, add a category property to each project object:
data() {
  return {
    projects: [
      {
        id: 1,
        title: 'Project 1',
        description: 'This is a description of project 1.',
        imageUrl: 'https://via.placeholder.com/300x200',
        link: '#',
        category: 'web',
      },
      {
        id: 2,
        title: 'Project 2',
        description: 'This is a description of project 2.',
        imageUrl: 'https://via.placeholder.com/300x200',
        link: '#',
        category: 'design',
      },
      // ... more projects
    ],
    selectedCategory: null, // Initially, no category is selected
  };
},
  1. Create a filter component: Add a new component called ProjectFilter.vue in the components directory:
<template>
  <div class="project-filter">
    <button @click="filterProjects(null)" :class="{ active: selectedCategory === null }">All</button>
    <button v-for="category in categories" :key="category" @click="filterProjects(category)" :class="{ active: selectedCategory === category }">
      {{ category }}
    </button>
  </div>
</template>

<script>
export default {
  props: {
    categories: {
      type: Array,
      required: true,
    },
    selectedCategory: {
      type: String,
      default: null,
    },
  },
  methods: {
    filterProjects(category) {
      this.$emit('filter', category);
    },
  },
};
</script>

<style scoped>
.project-filter {
  margin-bottom: 20px;
  text-align: center;
}

button {
  padding: 10px 20px;
  margin: 5px;
  border: 1px solid #ccc;
  background-color: #f0f0f0;
  cursor: pointer;
  border-radius: 5px;
}

button.active {
  background-color: #007bff;
  color: white;
  border-color: #007bff;
}
</style>

This component displays buttons for each category and emits a filter event when a button is clicked.

  1. Use the filter component in App.vue: Import and use the ProjectFilter component in App.vue:
<template>
  <div id="app">
    <h1>My Portfolio</h1>
    <ProjectFilter :categories="categories" :selectedCategory="selectedCategory" @filter="handleFilter" />
    <div class="projects-container">
      <ProjectCard v-for="project in filteredProjects" :key="project.id" :project="project" />
    </div>
  </div>
</template>

<script>
import ProjectCard from './components/ProjectCard.vue';
import ProjectFilter from './components/ProjectFilter.vue';

export default {
  components: {
    ProjectCard,
    ProjectFilter,
  },
  data() {
    return {
      projects: [
        {
          id: 1,
          title: 'Project 1',
          description: 'This is a description of project 1.',
          imageUrl: 'https://via.placeholder.com/300x200',
          link: '#',
          category: 'web',
        },
        {
          id: 2,
          title: 'Project 2',
          description: 'This is a description of project 2.',
          imageUrl: 'https://via.placeholder.com/300x200',
          link: '#',
          category: 'design',
        },
      ],
      selectedCategory: null,  // Initially, no category is selected
    };
  },
  computed: {
    categories() {
      return [...new Set(this.projects.map(project => project.category))]; // Get unique categories
    },
    filteredProjects() {
      if (!this.selectedCategory) {
        return this.projects;
      }
      return this.projects.filter(project => project.category === this.selectedCategory);
    },
  },
  methods: {
    handleFilter(category) {
      this.selectedCategory = category;
    },
  },
};
</script>

<style>
/* Existing styles */
</style>

Key changes in App.vue:

  • Import ProjectFilter: import ProjectFilter from './components/ProjectFilter.vue'; imports the new component.
  • Register ProjectFilter: ProjectFilter, registers the component.
  • Add categories data: Add a `selectedCategory` data property to keep track of the currently selected filter.
  • Add computed properties:
    • categories(): This computed property extracts the unique categories from your project data.
    • filteredProjects(): This computed property filters the projects based on the selectedCategory.
  • Add a method to handle the filter event: The handleFilter method updates the selectedCategory when the filter event is emitted from ProjectFilter.
  • Use the filter component: <ProjectFilter :categories="categories" :selectedCategory="selectedCategory" @filter="handleFilter" /> displays the filter component. It passes the categories and selected category as props, and listens for the `filter` event.
  • Use filteredProjects: <ProjectCard v-for="project in filteredProjects" ... /> now renders the filtered projects.

3. Project Details Modal

Instead of linking directly to each project, you can display more detailed information in a modal window. This improves the user experience by keeping them within your portfolio site.

  1. Create a ProjectDetailsModal.vue component: Create a new file ProjectDetailsModal.vue in the components directory:
<template>
  <div class="modal-overlay" v-if="showModal" @click.self="closeModal">
    <div class="modal-content">
      <span class="close-button" @click="closeModal">&times;</span>
      <img :src="project.imageUrl" :alt="project.title" />
      <h3>{{ project.title }}</h3>
      <p>{{ project.description }}</p>
      <a :href="project.link" target="_blank">View Project</a>
    </div>
  </div>
</template>

<script>
export default {
  props: {
    project: {
      type: Object,
      required: true,
    },
    showModal: {
      type: Boolean,
      required: true,
    },
  },
  methods: {
    closeModal() {
      this.$emit('close');
    },
  },
};
</script>

<style scoped>
.modal-overlay {
  position: fixed;
  top: 0;
  left: 0;
  width: 100%;
  height: 100%;
  background-color: rgba(0, 0, 0, 0.5);
  display: flex;
  justify-content: center;
  align-items: center;
  z-index: 1000;
}

.modal-content {
  background-color: white;
  padding: 20px;
  border-radius: 5px;
  position: relative;
  max-width: 80%;
  max-height: 80%;
  overflow-y: auto;
}

.close-button {
  position: absolute;
  top: 10px;
  right: 10px;
  font-size: 20px;
  cursor: pointer;
}

img {
  max-width: 100%;
  height: auto;
  margin-bottom: 10px;
}

a {
  display: inline-block;
  padding: 10px 20px;
  background-color: #007bff;
  color: white;
  text-decoration: none;
  border-radius: 5px;
}
</style>

This component displays a modal with project details. The showModal prop controls whether the modal is visible. The closeModal method emits a close event to close the modal.

  1. Update ProjectCard.vue to show the modal: Modify ProjectCard.vue to include a button that opens the modal and emits an event to the parent component. Replace the <a> tag with a button and add a click handler:
<template>
  <div class="project-card">
    <img :src="project.imageUrl" :alt="project.title" />
    <h3>{{ project.title }}</h3>
    <p>{{ project.description }}</p>
    <button @click="openModal">View Details</button>
  </div>
</template>

<script>
export default {
  props: {
    project: {
      type: Object,
      required: true,
    },
  },
  methods: {
    openModal() {
      this.$emit('open-modal', this.project);
    },
  },
};
</script>

<style scoped>
/* Existing styles */

button {
  padding: 10px 20px;
  background-color: #007bff;
  color: white;
  border: none;
  border-radius: 5px;
  cursor: pointer;
}
</style>
  1. Integrate the modal in App.vue: Import and use the ProjectDetailsModal component in App.vue:
<template>
  <div id="app">
    <h1>My Portfolio</h1>
    <ProjectFilter :categories="categories" :selectedCategory="selectedCategory" @filter="handleFilter" />
    <div class="projects-container">
      <ProjectCard
        v-for="project in filteredProjects"
        :key="project.id"
        :project="project"
        @open-modal="openModal"
      />
    </div>
    <ProjectDetailsModal
      :project="selectedProject"
      :showModal="showModal"
      @close="closeModal"
    />
  </div>
</template>

<script>
import ProjectCard from './components/ProjectCard.vue';
import ProjectFilter from './components/ProjectFilter.vue';
import ProjectDetailsModal from './components/ProjectDetailsModal.vue';

export default {
  components: {
    ProjectCard,
    ProjectFilter,
    ProjectDetailsModal,
  },
  data() {
    return {
      projects: [
        // ... your project data
      ],
      selectedCategory: null,
      showModal: false,   // Initially, the modal is hidden
      selectedProject: null, // Initially, no project is selected
    };
  },
  computed: {
    categories() {
      return [...new Set(this.projects.map(project => project.category))];
    },
    filteredProjects() {
      if (!this.selectedCategory) {
        return this.projects;
      }
      return this.projects.filter(project => project.category === this.selectedCategory);
    },
  },
  methods: {
    handleFilter(category) {
      this.selectedCategory = category;
    },
    openModal(project) {
      this.selectedProject = project;
      this.showModal = true;
    },
    closeModal() {
      this.showModal = false;
    },
  },
};
</script>

<style>
/* Existing styles */
</style>

Changes in App.vue:

  • Import ProjectDetailsModal: import ProjectDetailsModal from './components/ProjectDetailsModal.vue'; imports the modal component.
  • Register ProjectDetailsModal: Registers the modal component.
  • Add data for modal control: Add showModal and selectedProject data properties.
  • Add methods for modal control: The openModal and closeModal methods handle opening and closing the modal, and the openModal receives the selected project.
  • Use the modal component: The <ProjectDetailsModal> is rendered conditionally based on showModal. It passes the selectedProject and listens for the close event.
  • Pass the event handler to ProjectCard: The `ProjectCard` component emits the event, and this is handled in `App.vue` to open the modal.

Common Mistakes and How to Fix Them

Here are some common mistakes beginners often make when building Vue.js projects, along with how to fix them:

  • Incorrect Component Import/Registration: Forgetting to import and register components is a frequent issue. Make sure you correctly import the component using import MyComponent from './components/MyComponent.vue'; and then register it in the components object of your parent component.
  • Data Binding Issues: Incorrectly using Vue’s data binding directives (e.g., {{ }}, v-bind, v-model) can lead to unexpected behavior. Double-check your syntax and ensure you are binding to the correct data properties.
  • Prop Drilling: Passing props down multiple levels of nested components can become cumbersome. Consider using Vue’s provide/inject feature or a state management library like Vuex (or Pinia) for more complex applications.
  • Ignoring the Console: The browser’s developer console is your best friend. Errors, warnings, and debugging messages are invaluable for identifying and fixing problems. Learn to read and understand the console output.
  • Incorrect CSS Styling: Make sure your styles are scoped correctly (using scoped in the <style> tag) to avoid unintended style conflicts. Use the browser’s developer tools to inspect elements and identify style issues.
  • Forgetting the :key attribute in v-for loops: When rendering lists with v-for, always include a unique :key attribute for each item. This helps Vue efficiently update the DOM and can prevent unexpected behavior.

Key Takeaways

  • Vue.js is a great choice for building interactive web applications, especially for beginners.
  • Component-based architecture promotes code reusability and maintainability.
  • The Vue CLI simplifies project setup and development.
  • Understanding data binding, directives, and component communication is crucial.
  • Practice is key! Build small projects and experiment with different features.

Optional FAQ

Here are some frequently asked questions about building a portfolio with Vue.js:

  1. Can I deploy this portfolio to a live server? Yes, you can deploy your Vue.js portfolio to any web hosting service that supports static websites. Services like Netlify, Vercel, and GitHub Pages are popular choices. You’ll need to build your project for production using the command npm run build, which generates the necessary files for deployment.
  2. How can I make my portfolio SEO-friendly? To improve SEO, make sure your portfolio has descriptive titles and meta descriptions. Use semantic HTML elements, optimize your images, and ensure your website is responsive. Consider using a Vue.js SEO plugin or server-side rendering (SSR) for more advanced SEO techniques.
  3. What if I want to add more advanced features, like a blog? For more complex features, you might consider using a more advanced framework or a headless CMS (Content Management System) integrated with Vue.js. Frameworks like Nuxt.js, which is built on top of Vue.js, provide features like server-side rendering and static site generation, which are beneficial for SEO and performance.
  4. Where can I find more Vue.js resources? The official Vue.js documentation (https://vuejs.org/) is an excellent starting point. Other resources include tutorials on websites like Vue School, freeCodeCamp, and Udemy, as well as the Vue.js community on platforms like GitHub and Stack Overflow.

Building a portfolio with Vue.js is a rewarding experience that combines practical skill development with the creation of a valuable online presence. By following the steps outlined in this tutorial and experimenting with the different features, you’ll not only learn the fundamentals of Vue.js but also gain the confidence to showcase your work effectively. Remember to continuously refine your skills, embrace new challenges, and let your creativity flourish. The world of web development is ever-evolving, and with Vue.js as your companion, you’ll be well-equipped to navigate its exciting landscape and build a portfolio that truly reflects your unique talent and vision. Keep exploring, keep building, and never stop learning!