PortfolioWebsite: a Vite multi-page site with a two-stage Docker build
My personal website showcasing my work in Software Engineering and Graphic Design. A simple, multi-page static site built with HTML, CSS, and basic JavaScript. It’s designed to be a clean space where I can share my projects, my story, and my resume.
At a glance
- What is it?
- PortfolioWebsite is a personal site for a software engineer who also does graphic design, built as a Vite multi-page application with hand written CSS and ES6 modules, and shipped through an Nginx container. It suits anyone who wants a plain static site with a real build step, and it is one person's portfolio rather than a reusable template.
- Who is it for?
- Adopt PortfolioWebsite as a reference if you want a static multi-page site with a genuine build pipeline, since the separation between source styles, scripts and public assets, plus the two-stage Docker build, is a clean and small setup you can read in an afternoon. Do not adopt it as a template, because the content, the colour choices and the copy are one person's, and the package manifest marks it private and UNLICENSED, so there is no licence granting you reuse.
- Can I use it commercially?
- Not without permission. GitHub finds no licence file in the repository, and without a licence all rights are reserved by default: you may read the code but not reuse it. Check the README, or ask the authors, before using it.
- Is it still maintained?
- Yes. The repository last received commits 115 days ago.
- What is it written in?
- Mainly CSS, according to GitHub's language statistics.
Answers come from the project's GitHub data, last synced on October 23, 2026, and from our analysis. They are not legal advice.
Editorial analysis
A multi-page site, not a single page application
PortfolioWebsite is the personal site of Ahmad Hassan, also known as B-Ted, and the repository description calls it a simple, multi-page static site built with HTML, CSS and basic JavaScript, designed as a clean space to share projects, a story and a resume.
The multi-page point matters more than the word static might suggest. The top level of the repository is a set of HTML files sitting next to the assets and the styles: index.html as the main entry point, contact-me.html, my-story.html, work.html, hire-me.html and terms.html. There is no router and no client side view switching in the file list, which is the practical difference from a single page portfolio framework.
The pages that exist also tell you what the site is for. There is a contact page, a story page, a work page, a hire-me page and a terms page. A terms page is unusual for a personal portfolio and suggests the site is meant to do more than present work, which matches the .env file that carries a VITE_EMAIL, a VITE_PHONE, a LinkedIn URL and a Fiverr URL.
The README describes the project as a sophisticated, high-performance portfolio repository and says it bridges the gap between complex software engineering and high-fidelity UI and UX design. The technology badge lists HTML5, CSS3 and JavaScript as the stack, and the site is published at a GitHub Pages URL, so the live version is served by GitHub rather than by the container the repository also provides.
Vite is the only build dependency, and that is the design
The package manifest is short enough to read in full and it makes a statement. The name is ahmad-hassan-portfolio, the version is 1.0.0, and it is marked private and UNLICENSED. The devDependencies are vite at 5.0.0 or newer and prettier at 3.0.0 or newer, and there are no runtime dependencies at all.
That is unusual and worth naming, because it means the shipped site is plain HTML, CSS and JavaScript with a build step in front of it, and nothing else. No framework, no CSS preprocessor, no component library. A visitor downloads markup, styles and script and that is the whole payload.
The scripts match the standard Vite set. The dev script runs vite, the build script runs vite build, the preview script runs vite preview, and the format script runs prettier --write . on the whole directory.
The README's own description of the workflow table gives the three commands a developer needs: npm install to synchronise dependencies, npm run dev to start the reactive development server, and npm run build to generate the optimised production bundle. The prerequisites are Node.js at version 18.0 or higher, with Docker optional for containerised testing.
The claim of a reactive development server is the ordinary Vite hot module replacement, and the sequence diagram shows the developer running npm run dev and getting hot module replacement in return, then npm run build, then a production container serving the result through Nginx.
Source styles and scripts live apart from public assets
The repository layout separates three things that are often mixed together in a static site: the code, the configuration and the media.
Under src/ there is scripts/ for modularised JavaScript logic, styles/ for component specific and global CSS, and config/ for environment based configuration. The public/ directory holds assets/, with a hd/ subdirectory for raw high-fidelity source assets. Alongside those sit the Dockerfile, package.json, vite.config.js and index.html.
The design diagram makes the flow explicit. The browser loads the Vite pipeline output, which is static assets and HTML; those reference src/styles and src/scripts; the scripts in turn pull from public/assets. Inside the internal core, the styles resolve to global and component CSS and the scripts to modular ES6 logic. The asset management branch splits into optimized imagery and digital assets.
The reason to separate build-time logic from runtime assets, as the README puts it, is that the source tree can then be organised for the person maintaining it while the shipped directory stays minimal. The hd/ directory inside public/assets is a concrete example of that discipline: the raw high fidelity sources live next to the optimised versions rather than being mixed into the styles or scripts tree, so the pipeline can produce the optimised set without the originals shipping.
The top level of the repository also has an assets/ directory and a styles/ directory in addition to the src/ ones, plus scripts/, hd/, settings.json, a .env.example, a .vscode/ directory and a .editorconfig. So the documented structure and the actual top level are not identical, and the actual file list is the authority on where things ended up.
A two-stage Docker build that ends in Nginx
The Dockerfile is short and does the standard split. The build stage uses node:20-alpine, copies the package manifests, runs npm install, copies the rest of the source, and runs npm run build. The production stage uses nginx:stable-alpine and copies the built output from the build stage into the web root:
FROM node:20-alpine as build-stage
WORKDIR /app
COPY package*.json ./
RUN npm install
COPY . .
RUN npm run build
# Production stage
FROM nginx:stable-alpine as production-stage
COPY --from=build-stage /app/dist /usr/share/nginx/html
EXPOSE 80
CMD ["nginx", "-g", "daemon off;"]The multi-stage approach is what keeps the final image small. The Node.js toolchain and the npm cache stay in the build stage and never reach the image that runs, so the runtime is Nginx serving static files, and the whole container has one job.
The deployment diagram describes the same path from a different angle: code goes into the build pipeline, the optimization step splits into a bundled static distribution and image asset storage, and both feed the Nginx container that produces the production site. The workflow diagram puts Docker after the build, with the container serving the user.
One detail worth noting is the mismatch between the documented Node prerequisite and the container. The README asks for Node.js 18.0 or higher locally, while the container builds with node:20-alpine, so the build inside the image is on a newer runtime than the one you are told to install. That is harmless, but it means a local build and a container build are not guaranteed to be byte-identical.
Environment variables hold the contact details, and the example ships real ones
The .env.example file is small and every line in it is a Vite prefixed variable, which is how Vite exposes configuration to client side code.
The contact block carries VITE_EMAIL, VITE_PHONE, VITE_LINKEDIN and VITE_FIVERR. The SEO and meta block carries VITE_SITE_TITLE, set to B-Ted Portfolio, and VITE_SITE_DESCRIPTION, set to Designing with Pixels, Coding with Precision. The values in the example are the author's actual contact details rather than placeholders, which is a fork hazard worth handling first.
The reason to route this through environment variables rather than hard coding it in the HTML is that a fork should not have to hunt through templates to change an email address. In practice, with a Vite project, that means editing the .env file and rebuilding.
What the configuration does not cover is equally informative. There is no analytics variable, no theme toggle setting, no content directory and no build-time content pipeline. The pages are HTML files in the repository, so updating the portfolio means editing markup, not writing a post in a headless CMS. That is a deliberate simplicity for a site of this size, and it also means the site has no way to be content-driven without replacing that approach.
The licence position needs attention before you reuse any of it. The package manifest declares the project private with a licence of UNLICENSED, the repository licence is reported as unknown, and there is no LICENSE file among the top level entries. So there is no grant permitting a fork, and this article does not give legal advice: the practical point is that you can read the code and learn from it, and you should ask the author before shipping a derivative.
Editorial conclusion
Adopt PortfolioWebsite as a reference if you want a static multi-page site with a genuine build pipeline, since the separation between source styles, scripts and public assets, plus the two-stage Docker build, is a clean and small setup you can read in an afternoon. Do not adopt it as a template, because the content, the colour choices and the copy are one person's, and the package manifest marks it private and UNLICENSED, so there is no licence granting you reuse. Before you fork it, change the contact details in the .env file, since the shipped example contains the author's real email address and phone number, and remember that the only dependency is the build tool itself, so anything you add is yours to maintain.
Frequently asked questions
How do I build and run the PortfolioWebsite project?
Install Node.js 18.0 or higher, then run `npm install` to synchronise dependencies, `npm run dev` to start the development server with hot module replacement, and `npm run build` to generate the production bundle. A preview script is also available, and Prettier formats the project with `npm run format`.
How is the PortfolioWebsite project deployed?
The Dockerfile uses a two-stage build, with node:20-alpine running npm install and npm run build, then nginx:stable-alpine copying the built output from /app/dist into /usr/share/nginx/html, exposing port 80. The live site is published on GitHub Pages at ahmadhassan-bted.github.io.
Where do I change the contact details on the PortfolioWebsite?
In the .env file, which Vite exposes through VITE_ prefixed variables. The example sets VITE_EMAIL, VITE_PHONE, VITE_LINKEDIN and VITE_FIVERR, along with VITE_SITE_TITLE and VITE_SITE_DESCRIPTION, and the shipped values are the author's real contact details rather than placeholders.
What does the PortfolioWebsite project use as its dependencies?
Only two dev dependencies, vite at 5.0.0 or newer and prettier at 3.0.0 or newer, with no runtime dependencies at all. The site itself is plain HTML, CSS and JavaScript, with modular ES6 logic under src/scripts and global and component CSS under src/styles.
Official sources
Add this badge to your README
If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.
[](https://hysenlabs.com/projects/ahmadhassan-bted-portfoliowebsite)Was this useful?