Python Flask Portfolio Website Project

Written by

in

Looking to showcase your projects, skills, and personal brand online? A Python Flask portfolio website offers a lightweight, customizable, and SEO‑friendly solution that can grow with you. In this guide we’ll walk through every step of building a polished portfolio from scratch—setting up the environment, crafting clean routes, styling with Jinja2 templates, adding a functional contact form, and deploying the final product. Whether you’re a recent graduate, a freelance developer, or simply a tech enthusiast, this Flask portfolio project will give you a professional web presence while sharpening your Python web‑development chops.

Why Flask Is the Ideal Choice for a Portfolio Site

Flask is a micro‑framework that provides just enough structure to get a web app running without the overhead of larger frameworks like Django. Here are the key reasons it shines for a personal portfolio:

  • Flexibility: You decide the folder layout, templating engine, and extensions, so the site can stay as simple or as complex as you need.
  • Fast Development Cycle: With built‑in development server and auto‑reloader, changes appear instantly, letting you iterate quickly.
  • SEO‑Friendly: Flask renders HTML on the server, making it easy for search engines to crawl and index your content.
  • Lightweight Hosting: Most PaaS providers (Heroku, Render, Fly.io) support Flask out of the box, keeping deployment costs low.
  • Python Ecosystem: Leverage powerful libraries—SQLAlchemy for data, WTForms for forms, and Flask‑Mail for email handling—without learning a new language.

Project Overview: What the Portfolio Will Include

Our final portfolio will contain the following core pages and features:

  1. Home: A hero section with a brief intro, animated background, and call‑to‑action buttons.
  2. About: A narrative about your background, skills, and professional journey.
  3. Projects: A dynamic grid that pulls project data from a JSON file or simple SQLite database.
  4. Blog (optional): A lightweight blog powered by Markdown files for SEO‑rich content.
  5. Contact: A secure contact form that sends messages to your email via Flask‑Mail.

All pages will share a common base template, use Bootstrap 5 for responsive design, and include meta tags optimized for search engines.

Setting Up the Development Environment

Before writing any code, ensure your workstation is ready for Flask development.

# 1. Install Python (>=3.9) and create a virtual environment
python3 -m venv venv
source venv/bin/activate   # On Windows use `venv\Scripts\activate`

# 2. Install Flask and essential extensions
pip install Flask Flask-WTF Flask-Mail Flask-Login

# 3. Verify installation
python -c "import flask; print(flask.__version__)"

Keeping dependencies isolated in a venv prevents version conflicts and makes deployment smoother.

Building the Core Routes with Flask

Flask routes map URLs to Python functions. Below is a minimal app.py that defines the main pages of our portfolio.

from flask import Flask, render_template, request, flash, redirect, url_for
from flask_mail import Mail, Message

app = Flask(__name__)
app.config['SECRET_KEY'] = 'replace-with-a-secure-key'

# Mail configuration (example using Gmail)
app.config.update(
    MAIL_SERVER='smtp.gmail.com',
    MAIL_PORT=587,
    MAIL_USE_TLS=True,
    MAIL_USERNAME='your.email@gmail.com',
    MAIL_PASSWORD='your-app-password'
)
mail = Mail(app)

@app.route('/')
def home():
    return render_template('home.html', title='Home')

@app.route('/about')
def about():
    return render_template('about.html', title='About Me')

@app.route('/projects')
def projects():
    # In a real app, load projects from a database or JSON file
    project_list = [
        {'name': 'Flask Blog', 'description': 'A simple blog built with Flask.'},
        {'name': 'Data Dashboard', 'description': 'Interactive charts using Plotly.'}
    ]
    return render_template('projects.html', title='My Projects', projects=project_list)

@app.route('/contact', methods=['GET', 'POST'])
def contact():
    if request.method == 'POST':
        name = request.form['name']
        email = request.form['email']
        message_body = request.form['message']

        msg = Message(subject=f'Portfolio Contact from {name}',
                      sender=app.config['MAIL_USERNAME'],
                      recipients=['your.email@gmail.com'],
                      body=f'From: {name} <{email}>\n\n{message_body}')
        mail.send(msg)
        flash('Your message has been sent successfully!', 'success')
        return redirect(url_for('contact'))

    return render_template('contact.html', title='Contact Me')
    
if __name__ == '__main__':
    app.run(debug=True)

Notice the use of render_template to keep HTML separate from Python logic—this is a best practice for maintainability and SEO.

Designing the Layout with Jinja2 Templates

Flask uses Jinja2 as its templating engine. Create a templates/ folder with a base layout that all pages extend.

<!-- templates/base.html -->
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>{{ title }} | My Flask Portfolio</title>
    <meta name="description" content="Showcasing my projects and skills built with Python Flask.">
    <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/css/bootstrap.min.css" rel="stylesheet">
    <link rel="stylesheet" href="{{ url_for('static', filename='style.css') }}">
</head>
<body>
    <nav class="navbar navbar-expand-lg navbar-dark bg-dark">
        <div class="container-fluid">
            <a class="navbar-brand" href="{{ url_for('home') }}">My Portfolio</a>
            <button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarNav">
                <span class="navbar-toggler-icon"></span>
            </button>
            <div class="collapse navbar-collapse" id="navbarNav">
                <ul class="navbar-nav ms-auto">
                    <li class="nav-item"><a class="nav-link" href="{{ url_for('home') }}">Home</a></li>
                    <li class="nav-item"><a class="nav-link" href="{{ url_for('about') }}">About</a></li>
                    <li class="nav-item"><a class="nav-link" href="{{ url_for('projects') }}">Projects</a></li>
                    <li class="nav-item"><a class="nav-link" href="{{ url_for('contact') }}">Contact</a></li>
                </ul>
            
</nav> <main class="container my-5"> {% block content %}{% endblock %} </main> <footer class="bg-dark text-white text-center py-3"> &copy; {{ current_year }} My Flask Portfolio. All rights reserved. </footer> <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/js/bootstrap.bundle.min.js"></script> </body> </html>

Each page (e.g., home.html, about.html) will extend base.html and fill the {% block content %} section. This structure keeps the site DRY and improves SEO because the meta tags live in a single place.

Adding a Secure Contact Form

Using Flask-WTF gives us CSRF protection and easy validation.

# forms.py
from flask_wtf import FlaskForm
from wtforms import StringField, TextAreaField, SubmitField
from wtforms.validators import DataRequired, Email, Length

class ContactForm(FlaskForm):
name = StringField('Name', validators=[DataRequired(), Length(max=50)])
email = StringField('Email', validators=[DataRequired(), Email(), Length(max=120)])
message = TextAreaField('Message', validators=[DataRequired(), Length(max=100

Comments

Leave a Reply

Your email address will not be published. Required fields are marked *

More posts