DEV Community

Cover image for I built a portfolio in minutes with Bolt and Manifest
Sébastien Conejo
Sébastien Conejo

Posted on

I built a portfolio in minutes with Bolt and Manifest

TL;DR

In this article, I'll show you how to build a complete portfolio with a backend to manage your projects. We'll use bolt for the frontend and Manifest to spin up a backend in seconds. No complexity, no bloat—just a fast, efficient, and streamlined workflow. Your portfolio will be ready to deploy in minutes.

Introduction

As developers, We often need to build content-driven websites like blogs, portfolios, or showcases, whether for personal use or clients. Most backend solutions are bloated. They require long setups, endless tutorials, and unnecessary complexity just to get started.

Building a modern portfolio with content management shouldn’t be a burden.

Manifest fixes this. No over-engineering, no wasted time. Just a backend that works instantly.

Image description

Prerequisites

Before getting started, make sure you have Node.js and npm installed on your machine.

Used Tools

Here’s an overview of the tools We’ll be working with:

  • Bolt.new: To generate a portfolio frontend from a simple prompt.
  • Manifest: To generate a complete backend. It includes a documented API, a database, an admin panel with authentication, and storage for images, and much more.

Setting Up the Project

The goal here is to show you how to easily add a backend to any frontend. I won’t go into too much detail on frontend development. Instead, I’ll simply add a backend to a random portfolio frontend generated by Bolt.new.

Initializing the Frontend with Bolt.new

  1. Let's start by generating the frontend. Head over to bolt.new and enter a prompt. I used: 'Create a beautiful one-page portfolio with a contact form.'

  2. Bolt will then generate a set of specs. We can iterate on them until we're happy with the proposed frontend.

Overview of our portfolio

After a few iterations, I ended up with the following portfolio.

Image description

Let’s take a look at the project’s code. The generated structure is clean, with each section of the site having its own .tsx component.

Image description

These files handle only the frontend. There's no business logic yet. We'll use Manifest to add a backend and make the projects dynamic.

Bolt doesn't yet have direct integration with Manifest, but adding it manually is still quick and easy. ⚡

Initialize the backend with manifest

Still from bolt.new, we'll open a second terminal and install manifest using the following command:

  npx add-manifest
Enter fullscreen mode Exit fullscreen mode

After installing the backend, a Manifest repository appears at the root of the project.

Image description

Now, let’s define our data model. Since projects are defined by the following fields:

  • Project title
  • Image with two different sizes
  • Description
  • External link
  • Role
  • Date

Let's open the manifest/backend.yml file and replace the existing code with this:

name: my portfolio
    entities:
      Project:
        properties:
          - title
          - description
          - role
          - { name: date, type: date }
          - { name: url, type: link }
          - {
              name: photo,
              type: image,
              options:
                {
                  sizes:
                    {
                      small: { height: 403, width: 805 },
                      large: { height: 806, width: 1610 },
                    },
                },
            }
      Contacts:
        properties:
          - name
          - email
          - { name: message, type: text }
Enter fullscreen mode Exit fullscreen mode

Starting the backend

Still from our second terminal, We run the following command:

  manifest run dev
Enter fullscreen mode Exit fullscreen mode

When the backend is up and running, the terminal provides two links:

From the "preview" button in bolt, We can choose to view either the portfolio frontend, the admin panel, or the API documentation.

Image description

Let's open the admin panel (http://localhost:1111) and log in using the pre-filled credentials.

In the admin panel sidebar, We'll click on "Collections", then, "Projects". We should now see an empty list of projects.

Image description

Let's click "Create a new project", fill in all the fields, and submit the project.

Image description

Once our project is submitted, We should see it appear in the project list.

Image description

Replacing static data with dynamic content

Now, we need to replace the static projects with data from the backend. The first step is to install the Manifest SDK so we can interact with our backend.

Installing the Manifest SDK

Open another terminal and install the manifest SDK:

npm i @mnfst/sdk  
Enter fullscreen mode Exit fullscreen mode

Use the Manifest JS SDK to fetch our data

Once the installation is complete, We'll open the work.tsx file and take a look.

This file contains:

  1. Static data
  2. The HTML structure displaying that data
work.tsx

import { ExternalLink } from 'lucide-react'
import { FadeIn } from '../animations/FadeIn'

const projects = [
  {
    photo: {
      large: 'https://via.placeholder.com/800x450',
      small: 'https://via.placeholder.com/400x225',
    },
    title: 'Modern E-Commerce Platform',
    description: 'An intuitive e-commerce solution for small businesses.',
    role: 'Lead Designer',
    date: '2023-06-15',
    url: 'https://example.com/case-study-1',
  },
  {
    photo: {
      large: 'https://via.placeholder.com/800x450',
      small: 'https://via.placeholder.com/400x225',
    },
    title: 'AI-Powered Analytics Dashboard',
    description: 'A comprehensive analytics tool leveraging AI.',
    role: 'Frontend Developer',
    date: '2022-11-10',
    url: 'https://example.com/case-study-2',
  },
  {
    photo: {
      large: 'https://via.placeholder.com/800x450',
      small: 'https://via.placeholder.com/400x225',
    },
    title: 'Creative Portfolio Website',
    description: 'A portfolio showcasing creative work in digital design.',
    role: 'Full Stack Developer',
    date: '2021-09-20',
    url: 'https://example.com/case-study-3',
  },
]
export function Work() {
  return (
    <section id="work" className="py-32 bg-white">
      <div className="container mx-auto px-6">
        <FadeIn>
          <h2 className="text-4xl md:text-5xl font-bold mb-16 text-center">
            Selected Work
          </h2>
        </FadeIn>

        <div className="space-y-32">
          {projects.map((project, index) => (
            <FadeIn key={index} delay={index * 0.2}>
              <div className="group">
                <div
                  className={`grid md:grid-cols-3 gap-12 items-center ${
                    index % 2 === 1 ? 'md:grid-flow-dense' : ''
                  }`}
                >
                  <div
                    className={`md:col-span-2 ${
                      index % 2 === 1 ? 'md:col-start-2' : ''
                    }`}
                  >
                    <div className="relative aspect-[16/9] overflow-hidden rounded-xl">
                      <img
                        src={project.photo.large || project.photo.small}
                        alt={project.title}
                        className="w-full h-full object-cover bg-slate-100 transition-transform duration-700 group-hover:scale-105"
                      />
                    </div>
                  </div>

                  <div className="space-y-6">
                    <h3 className="text-3xl font-bold">{project.title}</h3>
                    <p className="text-gray-600 text-lg leading-relaxed">
                      {project.description}
                    </p>

                    <div className="flex flex-col gap-4">
                      <div>
                        <p className="text-sm text-gray-500">Role</p>
                        <p className="text-lg font-medium">{project.role}</p>
                      </div>
                      <div>
                        <p className="text-sm text-gray-500">Date</p>
                        <p className="text-lg font-medium">{project.date}</p>
                      </div>
                      <a
                        href={project.url}
                        className="inline-flex items-center gap-2 text-blue-600 hover:text-blue-700 font-medium"
                      >
                        View Case Study <ExternalLink size={18} />
                      </a>
                    </div>
                  </div>
                </div>
              </div>
            </FadeIn>
          ))}
        </div>
      </div>
    </section>
  )
}

Enter fullscreen mode Exit fullscreen mode

To connect work.tsx to the backend, We first need to import the manifest SDK. Let's add this import at the top of the file:

import Manifest from '@mnfst/sdk'
Enter fullscreen mode Exit fullscreen mode

Now, We'll add the types/types.ts file and define the project interface:

export interface Project {  
  photo: { large: string; small: string }  
  title: string  
  description: string  
  role: string  
  date: string  
  url: string  
}  

Enter fullscreen mode Exit fullscreen mode

This file provides a clear and reusable type definition for projects.

Next, in work.tsx, We'll remove the static code. In my case, it looks like this:


/* [...] */
const projects = [
  {
    photo: {
      large: 'https://via.placeholder.com/800x450',
      small: 'https://via.placeholder.com/400x225',
    },
    title: 'Modern E-Commerce Platform',
    description: 'An intuitive e-commerce solution for small businesses.',
    role: 'Lead Designer',
    date: '2023-06-15',
    url: 'https://example.com/case-study-1',
  },
  {
    photo: {
      large: 'https://via.placeholder.com/800x450',
      small: 'https://via.placeholder.com/400x225',
    },
    title: 'AI-Powered Analytics Dashboard',
    description: 'A comprehensive analytics tool leveraging AI.',
    role: 'Frontend Developer',
    date: '2022-11-10',
    url: 'https://example.com/case-study-2',
  }
]

export function Work() {
/* [...] */

Enter fullscreen mode Exit fullscreen mode

and replace it with:

/* [...] */
import Manifest from '@mnfst/sdk'
import { useEffect, useState } from 'react'
import { Project } from '../../types/types'

export function Work() {
  const [projects, setProjects] = useState<Project[]>([])

  useEffect(() => {
    const fetchProjects = async () => {
      const manifest = new Manifest('http://localhost:1111')
      const paginator = await manifest
        .from('projects')
        .orderBy('date', { desc: true })
        .find()
      const projects = paginator.data as Project[]
      setProjects(projects)
    }
    fetchProjects()
  }, [])
/* [...] */
Enter fullscreen mode Exit fullscreen mode

I used two React hooks to manage the data flow:

  • useState: Handles local state inside a functional component.
  • useEffect: Runs code in response to state changes, component mounting, or unmounting.

Let's check the portfolio interface. The newly added project should now appear.

Image description

At this point, the static content has been successfully replaced with dynamic data from Manifest! 🚀

Projects are now loaded directly from the database and displayed dynamically on the frontend.

Even better, it's now possible to add, edit, or delete projects directly from the admin panel! 🎯

Conclusion

With just a few steps, we’ve turned a static portfolio into a dynamic, fully managed application. What’s powerful here isn’t just the speed of setup—it’s the flexibility this approach provides.

This isn’t just about portfolios. The same workflow can be applied to blogs, dashboards, or any content-driven project. With Manifest handling the backend, you’re free to iterate, scale, and focus on the frontend experience rather than backend maintenance.

Next Steps

With the project ready, the next logical step is to download it and push it to GitHub before deployment. This ensures proper version control and makes future updates easier.

Manifest is built for speed and simplicity. From setup to deployment, everything is designed to be as smooth and effortless as possible. Deployment is just as fast. With a few steps, your backend is live.

Plus, Manifest Cloud is in development, allowing you to deploy instantly, without any setup. If you want to be notified when this feature launches, sign up here:

→ Manifest Cloud waitlist

🔹 Live portfolio on StackBlitz
🔹 GitHub repository

If you found this post useful, feel free to share it with others who might appreciate it.

Image description

Top comments (0)