Building Portfolio in Next.js
Tutorials
Building Portfolio in Next.js

By Subash Maharjan


Chapters
  • Chapter 1

    Introduction

  • Chapter 2

    Project Architecture

  • Chapter 3

    SCSS Design Tokens

  • Chapter 4

    The Hero Component

  • Chapter 5

    The MDX Content Layer

  • Chapter 6

    Syntax Highlighting & Project Filters

  • Chapter 7

    Smooth Layout Transitions

  • Chapter 8

    Modern Contact Forms

  • Chapter 9

    SEO & Deployment

  • Chapter 10

    Lighthouse Performance & Blogging

  • Chapter 11

    Interactive Playgrounds & Subscriptions

  • Chapter 12

    Dynamic Social Previews & Launch

  • Chapter 13

    Project Summary for Recruiters

  • building portfolio in nextjs

    Modern Contact Forms

    Chapter 8

    2 min read

    On this page
    1. The Server Action (`actions.ts`)2. The Contact Form (ContactForm.tsx)3. Styling with Mixins (ContactForm.module.scss)
    Company LogoSubash
    HOMEABOUTPORTFOLIOCONTENTSCONTACT

    The final piece of your portfolio is the Contact Form. By using Next.js Server Actions, you can handle form submissions directly on the server without creating a separate API route or requiring client-side JS for the submission to work.

    1. The Server Action (actions.ts)

    This is a secure, server-side function that processes the form data. You can integrate it with Nodemailer or a service like Resend.

    // src/app/contact/actions.ts
    "use server";
     
    export async function sendEmail(formData: FormData) {
      const name = formData.get('name');
      const email = formData.get('email');
      const message = formData.get('message');
     
      // Logic to send email (e.g., via Resend)
      console.log(`Message from ${name} (${email}): ${message}`);
     
      return { success: true };
    }

    2. The Contact Form (ContactForm.tsx)

    We use a Client Component only to handle the "Pending" state and "Success" notifications for a better user experience.

    "use client";
    import { useFormStatus } from 'react-dom';
    import { sendEmail } from './actions';
    import styles from './ContactForm.module.scss';
     
    function SubmitButton() {
      const { pending } = useFormStatus();
      return (
        <button type="submit" disabled={pending} className={styles.submitBtn}>
          {pending ? 'Sending...' : 'Send Message'}
        </button>
      );
    }
     
    export default function ContactForm() {
      return (
        <form action={sendEmail} className={styles.form}>
          <div className={styles.field}>
            <label htmlFor="name">Name</label>
            <input type="text" id="name" name="name" required />
          </div>
     
          <div className={styles.field}>
            <label htmlFor="email">Email</label>
            <input type="email" id="email" name="email" required />
          </div>
     
          <div className={styles.field}>
            <label htmlFor="message">Message</label>
            <textarea id="message" name="message" rows={5} required />
          </div>
     
          <SubmitButton />
        </form>
      );
    }

    3. Styling with Mixins (ContactForm.module.scss)

    We’ll use our @include dark mixin to ensure the form inputs remain readable in all themes.

    .form {
      max-width: 500px;
      margin: $spacing-lg auto;
     
      .field {
        margin-bottom: $spacing-sm;
     
        label {
          display: block;
          margin-bottom: 0.5rem;
          font-weight: 600;
        }
     
        input, textarea {
          width: 100%;
          padding: 0.8rem;
          border: 1px solid #ddd;
          border-radius: 4px;
          background: $color-white;
     
          @include dark {
            background: #1a1a1a;
            border-color: #333;
            color: $text-dark;
          }
        }
      }
     
      .submitBtn {
        background: $color-crimson;
        color: white;
        padding: 1rem 2rem;
        border: none;
        cursor: pointer;
        transition: opacity 0.2s;
     
        &:disabled {
          opacity: 0.5;
        }
      }
    }

    Why Server Actions?

    • Zero Bundle Size: No need for large form libraries like Formik or Yup for simple validations.
    • Progressive Enhancement: The form works even if the user has disabled JavaScript.
    • Security: Logic is executed on the server, keeping your API keys and environment variables safe.
    HOME
    ABOUT
    PORTFOLIO
    CONTENTS
    CONTACT

    © 2026Subash Maharjan™

    Made byHudeoworks Design