Open-source project
cssninjaStudio/fresh avatar
cssninjaStudio/fresh

Fresh is an Astro landing page shell that arrives with Alpine and Bulma already wired

🚀 Free Astro Landing page starter

671 stars150 forksSCSSMIT

At a glance

What is it?
Fresh is a free landing page starter from cssninjaStudio, built on Astro 4, Bulma 0.9 and Alpine 3. It is a template you edit rather than a library you depend on, and its pinned dependency set, Docker setup and uneven release history define what you inherit.
Who is it for?
Fresh fits a team that wants a polished marketing page in a day and is comfortable editing Astro pages and SCSS under src/ rather than designing a component system. Skip it if you need a current dependency baseline, since Astro is pinned to 4.7.0 and Bulma to 0.9.4, or if you want the demo image to reflect your own code, because the compose file runs digisquad/cssninja.fresh-demo:latest.
Can I use it commercially?
Yes. MIT is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
Is it still maintained?
Yes. The repository last received commits 23 days ago.
What is it written in?
Mainly SCSS, 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 template you own, not a package you install

Fresh is published as a private package. The package.json declares the name fresh, version 5.4.0, private as true and type as module, so it is a source tree you clone and edit rather than a dependency you add to your own project.

The working set of files is small and conventional for an Astro site. The top level holds .astro/, src/, public/, astro.config.mjs, postcss.config.cjs and tsconfig.json, alongside a .npmrc, a pnpm-lock.yaml and a .vscode/ directory. SCSS is the primary language of the repository. The dependency list includes sass 1.75.0 and autoprefixer 10.4.19, and a postcss.config.cjs sits at the root, so stylesheets are compiled rather than shipped as plain CSS.

The result is a starting point, not a framework. Nothing in the three core packages constrains what your finished site looks like, and there is no runtime contract you inherit. The trade-off is that you own everything a real site still needs, because what ships here is a landing page and not a product application.

The pinned stack is Astro 4.7.0, Bulma 0.9.4 and Alpine 3.13.10

The README advertises Astro v4.x, Bulma 0.9.x and Alpine v3.x, and package.json pins exact versions for all three: astro 4.7.0, bulma 0.9.4 and alpinejs 3.13.10. Exact pins plus a committed pnpm-lock.yaml mean your install reproduces the author's tree rather than resolving newer minors on its own.

Around that core sits a wider set of runtime dependencies. Alpine is extended with three plugins, @alpinejs/collapse, @alpinejs/intersect and @alpinejs/persist, each at 3.13.10. Bulma is paired with bulma-css-vars 0.8.0, which is what lets CSS variables drive the theme. Then come the JavaScript widgets a landing page tends to accumulate: plyr 3.7.8 for media players, simplebar 6.2.5 for custom scrollbars, lozad 1.16.0 for lazy loading, notyf 3.10.0 for notifications, js-datepicker 5.18.2 and fuse.js 7.0.0 for search, moment 2.30.1 for dates, and iconify-icon for icons. That is sixteen runtime dependencies in total.

The list is a fair summary of the template's character. It is a page, with the page's usual baggage, including a full date library and two separate date or picker packages. Removing any of them is straightforward, but each one is present because some part of the demo uses it, and deleting one means finding that usage first.

pnpm dev, then the run-s build:* pipeline

The README documents three commands, and the package.json scripts make the third one more interesting than it first appears.

bash
pnpm i

Installs the dependencies from the committed pnpm-lock.yaml. The engines field requires Node.js 18 or later and pnpm 8 or later, so check both before the first install instead of discovering the mismatch partway through.

bash
pnpm dev

Starts the Astro dev server. The same script is registered twice, with dev and start both mapped to astro dev, which is a convenience for hosts that expect a start command.

bash
pnpm build

This one does not run a single compiler. The build script is run-s build:*, so npm-run-all executes every matching script in sequence, and the only match today is build:astro, which runs astro build. The wildcard form means a future step, such as a minify pass or an image step, can be added as build:something without touching the build entry point. The output lands in dist/, which is the directory the Dockerfile later copies into the web server image.

A two stage Dockerfile that ends in nginx

The Dockerfile builds the site with Node and serves it with nginx, using bitnami base images. The build stage looks like this:

dockerfile
FROM bitnami/node:20 AS build
WORKDIR /app

RUN corepack enable

COPY package.json ./
COPY pnpm-lock.yaml ./
COPY .npmrc ./
RUN pnpm install --frozen-lockfile

Corepack is enabled so pnpm comes from the package manager metadata rather than a global install, and the install uses --frozen-lockfile, which fails the build rather than silently updating pnpm-lock.yaml. Only the manifest, the lockfile and the npmrc are copied before installation, so the dependency layer stays cached while the site source changes around it.

The runtime stage swaps the Node image for a web server:

dockerfile
FROM bitnami/nginx:1.25 AS prod
WORKDIR /app

COPY --from=build /app/dist .
COPY ./nginx/alpinejs.conf /opt/bitnami/nginx/conf/server_blocks/nginx.conf

The built dist directory becomes the nginx document root, and the repository's own nginx/alpinejs.conf replaces the default server block. That last part is the detail worth understanding before you deploy, since the nginx/ directory holds the configuration the image depends on and the file name points at serving Alpine's client side code.

docker-compose.yml runs a published image, not your build

The compose file in the repository is a demo harness rather than a deployment recipe for your own code. Its service section is short:

yaml
services:
  fresh-demo:
    image: digisquad/cssninja.fresh-demo:latest
    networks:
      - cssninja-services

There is no build directive and no reference to the Dockerfile, so bringing the stack up pulls a published image tagged latest from the digisquad organisation instead of compiling the repository you just cloned. That is convenient for previewing the demo behind Traefik and misleading if you assume your edits are running. To ship your own changes, drop the image key and let compose build from the Dockerfile, or publish your own image and change the tag.

Two further constraints come with the file. The network is declared as external:

yaml
networks:
  cssninja-services:
    external: true

An external network is not created by compose, it has to exist already, so a first run on a clean machine fails on the missing network unless you create it yourself or change the declaration. The service also carries Traefik labels for host routing and TLS, including a Host rule of fresh.${HOST:-127.0.0.1.nip.io} that falls back to the wildcard nip.io domain, so a local demo resolves over https without a hosts file entry. The labels also reference a certresolver named http, so that resolver has to exist in whatever Traefik instance picks the container up.

v5.4.0 landed in 2026 after a two year gap

The release history is short and uneven. Fresh v5.2.0 was published on 2023-09-17, v5.3.0 on 2024-04-27, and v5.4.0 on 2026-09-30. The last push was on 2026-09-30, the same day as the v5.4.0 tag, and the repository is not archived, so the project is not dormant.

The gap still matters because of what the pins look like. Astro 4.7.0 sits inside the 4.x line rather than at the current release, Bulma 0.9.4 is below the 1.x major, and Alpine is held at 3.13.10 with three plugins on the same version. A template that stood unchanged for two years carries two years of dependency drift, and the pinned numbers are exactly what a lockfile preserves.

The upgrade path is constrained by that same choice. The stack is a set of exact versions plus a committed lockfile, so moving to a newer Astro is an editing job rather than a version bump. For contributors, the README asks for a short checklist in each issue (issue definition, expected behaviour, actual behaviour, steps to reproduce, and any attempted fixes), and CHANGELOG.md is where the history lives for anyone deciding how far to backport.

When a plain Astro site is the better answer

Fresh makes sense when the deliverable is a marketing or landing page and the team wants a finished look without building a design system first. You get Bulma's layout primitives, Alpine's interactivity with no framework runtime, an SCSS entry point, and a media player, a search box, a datepicker and toast notifications already wired.

It is the wrong choice when you want a leaner dependency surface. The runtime list runs to sixteen packages, and a landing page template that carries plyr, simplebar, notyf, moment and js-datepicker hands you those libraries to maintain whether or not your copy keeps the matching sections. A site built directly on Astro with Bulma and Alpine reaches the same core with three packages instead of sixteen, at the cost of losing the finished styling.

The other boundary is scope. This is a landing page starter with a fixed page structure. A product dashboard, a documentation site or an application with real routing and state does not fit that shape, and the volume of Astro and SCSS you would rewrite tends to exceed the volume you keep. The licence is MIT, so there is no restriction stated on commercial use or modification of the template itself, though each bundled library carries its own terms.

Editorial conclusion

Fresh fits a team that wants a polished marketing page in a day and is comfortable editing Astro pages and SCSS under src/ rather than designing a component system. Skip it if you need a current dependency baseline, since Astro is pinned to 4.7.0 and Bulma to 0.9.4, or if you want the demo image to reflect your own code, because the compose file runs digisquad/cssninja.fresh-demo:latest. Before starting, run pnpm i with pnpm 8 or later on Node.js 18 or later, confirm the external cssninja-services network exists if you use docker-compose.yml, and read CHANGELOG.md to see what changed in v5.4.0.

Frequently asked questions

How do I install and run the Fresh landing page template locally?

Clone the repository and run pnpm i, then pnpm dev to start the Astro dev server. The engines field requires Node.js 18 or later and pnpm 8 or later, and the build command pnpm build runs every build:* script through npm-run-all.

Which frameworks does Fresh use?

Fresh is built on Astro, Bulma and Alpine JS. package.json pins astro 4.7.0, bulma 0.9.4 and alpinejs 3.13.10, with bulma-css-vars 0.8.0 alongside Bulma and the collapse, intersect and persist Alpine plugins.

Does docker-compose.yml build my local changes?

No. The compose file references the published image digisquad/cssninja.fresh-demo:latest with no build directive, and it expects an external network named cssninja-services that it does not create. Use the Dockerfile in the repository to build your own version.

How do I deploy Fresh in production?

The Dockerfile builds with bitnami/node:20 using pnpm install --frozen-lockfile, then copies dist/ into a bitnami/nginx:1.25 image and installs nginx/alpinejs.conf as the server block. The compose file adds Traefik host and TLS labels for the demo.

Is Fresh actively maintained?

The repository is not archived and the last push was on 2026-09-30, with the v5.4.0 tag published the same day. The gap before that release is wide though, since v5.3.0 was published on 2024-04-27 and v5.2.0 on 2023-09-17.

Official sources

  1. cssninjaStudio/fresh on GitHub
  2. License: MIT
  3. Project website
  4. README
  5. Releases
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.

Add this badge to your README

markdown
[![Hysen Labs](https://hysenlabs.com/badge/cssninjastudio-fresh.svg)](https://hysenlabs.com/projects/cssninjastudio-fresh)

Was this useful?