Vercel-like style portfolio template for developers.
Fork of slick-portfolio-svelte-5, customized for my portfolio. This fork adds a Nodemailer-powered contact form (via SvelteKit server actions), .env-based configuration, and guidance for managing images/SSR and content updates under src/lib/data.
For a reference, check out my personal protfolio.
The main libraries used here are :
shadcn-svelte: component library.tailwindcss: css styling.unocss: fonts and icons.- using
carbon(and thus the icon namesi-carbon-*) icons that could be browsed here
- using
mode-watcher: color mode utility.prismjs: markdown parsing in combination withmarked,marked-mangle,marked-gfm-heading-idanddompurify.
- Contact form wired to SvelteKit server actions and Nodemailer
- Environment-based configuration via
.env - Guidance for image paths (hero/bio images) with SvelteKit base path
- Notes about SSR and static hosting limits (GitHub Pages)
You can either clone or fork this repository :
You can fork the project, maintaining a link to the original repo using the fork button, make sure the check the Copy the master branch only checkbox.
- using
git:
# ssh
git clone git@github.com:RiadhAdrani/slick-portfolio-svelte-5.git protfolio
# https
git clone https://github.com/RiadhAdrani/slick-portfolio-svelte-5.git protfolio- using
degitnpm executable :
npx degit RiadhAdrani/slick-portfolio-svelte-5 portfolioif you don't have degit, it will prompt you to accept, enter y.
You can add the main repo as another remote repo to maintain updates in the future.
# ssh git remote add main git@github.com:RiadhAdrani/slick-portfolio-svelte-5.git
Before deploying to GitHub Pages:
If you forked the repo, go to the Actions tab in the newly generated repository, and enable workflows, click on the green button I understand my workflows, go ahead and enable them :
Allow GitHub Pages in your repo settings with correct permissions:
- go to your repo
Settings>Actions>General - in
Actions permissions: make sure thatAllow all actions and reusable workflowscheckbox is checked
Depending on the name of your repository, you would like to set the base variable to that, starting with a leading slash like this:
const base = '/slick-portfolio-svelte';But if your repository name is the same as your Github domain name; my Github name is RiadhAdrani so my domain name is riadhadrani.github.io (lower cased), and so the special repository name is also riadhadrani.github.io: if that is the case, you need to set the base to an empty string
const base = '';Note: This template uses
@sveltejs/adapter-staticfor GitHub Pages. Server actions do not run on static hosting. The contact form will work locally during development but will require a server-capable adapter (e.g., Vercel/Netlify/Node) or an external mail API in production. See "Contact form" below.
If you didn't commit and push the changes in the svelte.config.js yet, you can do that now, otherwise you can create an empty commit:
git commit --allow-empty -m "chore: trigger workflow"and push it to your master or main branch.
In the Actions tab, make sure that the Build and Deploy workflow is successful (wait for it to complete): you should have at least one successfull workflow run:
- go to your repo's
Settings>Pages - in Source section, select
Deploy from a branch. - in Branch section, select
gh-pagesand/ (root)and click on save
Again in the Actions tab, make sure that the pages-build-deployment workflow is successful (wait for it to complete): you should have at least one successfull workflow run:
If for some reason no action was launched, try pushing empty commit.
That's it, you can click on the latest deployment and visit it.
If you want to use the template as it is, you can :
-
update files in
src/lib/datawith your data. -
update
src/lib/index.cssfor custom styling. -
update
src/lib/index.cssfor custom markdown styling. -
update
static/favicon.pngto customize the tab's icon. -
You can find
shadcn-sveltecomponent insrc/lib/components/ui, other components are arranged by their page, or in thecommonfolder.
But feel free to explore and hack the template to your needs if you feel like it.
- Install deps and run:
npm i
npm run dev- Type/lint:
npm run check
npm run lintThis project includes a contact form at src/routes/contact. It posts to a SvelteKit server action in src/routes/contact/+page.server.ts, which sends mail via Nodemailer.
- Create
.envin the project root (same folder aspackage.json):
NODEMAILER_LOGIN=your-gmail@example.com # full Gmail address
NODEMAILER_PW=your-app-password # Gmail App Password (not your regular password)
FORWARD_MAIL_TO=recipient@example.com # where to receive messages-
Restart
npm run devafter changing.env. -
Local SSR is enabled in
src/routes/+layout.ts:
export const ssr = true;
export const prerender = false;- Production note (important): GitHub Pages (static) cannot run server actions, so the contact form cannot send emails there. To enable email in production, either:
- Deploy with a server-capable adapter (e.g., Vercel/Netlify/Cloudflare/Node), or
- Keep GitHub Pages and use a third-party/mail API (e.g., Resend/EmailJS/Formspree) from the client or via serverless functions.
- Most content lives in
src/lib/data/*.ts(e.g.,home.ts,bio.ts,experience.ts,education.ts,projects.ts,skills.ts,nav-bar.ts). Update text, links, and lists there. - Markdown content can live under
src/lib/data/md/and be rendered via the provided markdown components.
- Put public images in
static/images/...so they are served directly. - When referencing images in code that is rendered both on server and client, use SvelteKit’s base path:
import { base } from '$app/paths';
const heroImage = `${base}/images/full-picture-emilio.jpg`;- In Svelte components:
<script lang="ts">
import { base } from '$app/paths';
</script>
<img src={`${base}/images/full-picture-emilio.jpg`} alt="..." />- Alternatively, import images from
srcand use the imported URL:
<script lang="ts">
import hero from '$lib/assets/images/full-picture-emilio.jpg';
</script>
<img src={hero} alt="..." />If you see warnings about <button> inside <button>, use the asChild/builder pattern to avoid nested buttons:
<TooltipTrigger asChild let:builder>
<Button builders={[builder]}>Hover me</Button>
</TooltipTrigger>
<DialogTrigger asChild let:builder>
<Button builders={[builder]}>Open</Button>
</DialogTrigger>- Easiest: publish on Substack/Medium and render posts via RSS on a
/blogpage (instant updates, no redeploys) - Git-based CMS with in-repo content editing UI: Decap CMS or TinaCMS
- Hosted headless CMS: Contentful/Sanity/Hygraph (fetch at build; rebuild via webhooks)
- Svelte no longer support
node 14, use a newer version instead.





