Qressy
Back to blogDevelopment

How We Built and Shipped a Modern Logistics Company Website in One Day — Astro, Framer Motion, and Cloudflare

Manish Kumar··9 min read

Our logistics client came to us with exactly two assets: a PDF copy deck and a logo file. Their old reference prototype was offline, there was no CMS export, no design system, no staging environment — nothing. By the end of the same working day, a 19-page website was live on their subdomain: home, about, contact, six service pages, eight industry pages, and the index pages tying them together.

This post is the honest breakdown of how we did it — the stack choices, the one architectural decision that made 19 pages feasible in a day, a mid-project full re-theme, and a DNS wrinkle at deploy time that's worth knowing about. If you build marketing sites for clients, most of this is directly reusable.

Why Astro for a marketing site

For content-heavy marketing sites, Astro website development has become our default, and this project shows why. Astro ships zero JavaScript by default: every page is rendered to static HTML at build time, and the browser only downloads scripts you explicitly opt into. For a freight-forwarding company whose visitors are procurement managers on airport Wi-Fi, that matters — the pages are fast because there's almost nothing to load.

But the bigger win on a one-day timeline was Astro's comfort with content as data. We didn't write 14 service and industry pages by hand. Instead, all page copy lives in typed data files:

  • services.ts — six service entries: hero copy, feature lists, process steps, FAQs
  • industries.ts — eight industry entries with the same shape
  • faqs.ts — shared and page-specific question banks

Two dynamic-route templates — [service].astro and [industry].astro — render all 14 detail pages from that data. The templates own the markup; the data files own the words. When the client sends revised copy for Ocean Freight, we edit one object in one TypeScript file and never touch a component. TypeScript enforces the shape, so a missing FAQ or a typo'd field fails the build instead of shipping as a blank section.

Transcribing the client's PDF into structured data took about an hour. After that, adding a page was a data entry task, not a development task. That's the difference between shipping 19 pages in a day and shipping five.

Borrowing a design language, ethically

The client didn't have a design — but they had taste. They pointed us at a modern SaaS site they admired and said, make it feel like this. There's a right and a wrong way to handle that request. The wrong way is lifting assets, copy, illustrations, or CSS. The right way — the way design has always worked — is studying the patterns and rebuilding them from scratch for a different brand in a different industry:

  • A floating pill navbar that hovers over the hero, then docks into a full-width bar on scroll
  • Huge display headlines set in Space Grotesk
  • Monospace section labels like 01 · WHY US as wayfinding devices
  • A marquee ticker strip in the hero cycling through trade lanes and capabilities
  • Alternating dark/cream sections for rhythm down the page
  • A big rounded CTA block as the closer before the footer

Every pixel was ours: our grid, our components, our motion code, the client's copy, and licensed stock photography (ports, aircraft, warehouses, plus industry-specific shots for each of the eight industry pages) behind brand-tinted gradient overlays so mixed-source photos read as one family. Layout conventions aren't ownable; expression is. Borrow the former, never the latter.

The re-theme: why CSS custom properties turned a palette swap into 30 minutes

Mid-project, the brand direction changed: everything needed to move to the client's blue, #1A81C0. On a lot of codebases that's a day of find-and-replace archaeology. For us it was about 30 minutes, because from the first hour, no component ever referenced a raw color. Everything ran through CSS custom properties:

global.css
:root {
  --brand: #1a81c0;
  --brand-dark: #10598a;   /* section backgrounds */
  --brand-deep: #0a3a5c;   /* footer, CTA block */
  --brand-tint: #e8f3fa;   /* cream-side accents */
  --ink: #0e1b26;
  --paper: #faf7f2;
}

The dark shades aren't arbitrary — they're the brand hex with lightness pulled down and saturation nudged up in OKLCH, so they read as the same blue, deeper, rather than a different color. Derive them once, and the alternating dark sections, the photo overlay gradients, the accordion hover states, and the CTA block all re-themed themselves in one sweep. One file changed; 19 pages followed.

The lesson generalizes

Tokens are cheap on day one and priceless on day two. You can't predict a re-theme, but you can make it a non-event.

Framer Motion without React

Here's a detail that surprises people: this site has no React, but it uses Framer Motion animations throughout. The motion npm package ships a vanilla JavaScript API — the same spring physics, no framework required. In Astro, it lives in plain script tags, which Astro bundles per page. Three patterns did most of the work.

1. Spring-based scroll reveals

inView() fires when an element enters the viewport, and a spring gives the settle a physical feel that duration-based easing can't fake:

reveal.js
import { animate, inView } from "motion";

inView(".reveal", (el) => {
  animate(
    el,
    { opacity: [0, 1], y: [24, 0] },
    { type: "spring", stiffness: 120, damping: 18 }
  );
}, { amount: 0.4 });

2. SVG line drawing

The hero features a dotted world map with routes connecting ports worldwide. Each route is an SVG path, and the classic stroke-dashoffset trick makes them draw themselves in, staggered:

routes.js
import { animate, stagger } from "motion";

const routes = document.querySelectorAll(".route-path");
routes.forEach((path) => {
  const len = path.getTotalLength();
  path.style.strokeDasharray = String(len);
  path.style.strokeDashoffset = String(len);
});

animate(routes, { strokeDashoffset: 0 },
  { duration: 2.5, delay: stagger(0.2), ease: "easeInOut" });

3. Count-up stats

animate() also tweens raw values, which is all a stat counter needs:

stats.js
import { animate, inView } from "motion";

inView("[data-count]", (el) => {
  animate(0, Number(el.dataset.count), {
    duration: 1.8,
    ease: [0.16, 1, 0.3, 1],
    onUpdate: (v) => {
      el.textContent = Math.round(v).toLocaleString();
    },
  });
}, { amount: 1 });

On top of these: perpetual float loops on hero accents and the marquee strip. Every script checks matchMedia("(prefers-reduced-motion: reduce)") first and snaps elements to their final state if it matches. And because the content is static HTML underneath, a visitor with JavaScript disabled sees a complete page — the animations are decoration, never a dependency.

The SEO layer: structured data and 140 FAQs

Logistics is a research-heavy purchase, so we treated structured data SEO as a first-class feature rather than an afterthought.

FAQs at scale. Every service and industry page carries 10 FAQs — 140 across the site — written from the client's copy deck plus the questions freight buyers actually ask. The layout is a 50/50 split: a sticky heading on the left, accordions on the right, so long answer lists stay scannable. Each set emits FAQPage JSON-LD, making those answers eligible for rich results on exactly the long-tail queries a logistics buyer types.

The full schema stack. Alongside FAQPage: Organization (entity identity), WebSite (site-level metadata), Service on each service page (what's offered, by whom), and BreadcrumbList everywhere (clean hierarchy in results). Because pages render from typed data, the JSON-LD is generated from the same objects as the visible copy — the schema can't drift from the page.

Heading hierarchy. An audit flagged an h1-to-h3 skip — a decorative label had been marked up as a heading. We demoted it to a styled paragraph and restored the h1, h2, h3 chain. Small fix, but assistive tech and crawlers both navigate by outline, and it's the kind of thing that silently costs you.

Shipping: GitHub → Cloudflare Pages → a subdomain in another account

Deployment was the least dramatic part, which is the point of Cloudflare Pages deployment: push to GitHub, run wrangler pages deploy dist, and the static build is on Cloudflare's edge in about a minute.

The one wrinkle worth documenting: the client's DNS zone lived in a different Cloudflare account than our Pages project. You don't need to migrate anything. The flow:

  • In the Pages project, add the subdomain as a custom domain. Cloudflare detects the zone isn't in this account and switches to CNAME verification.
  • In the client's account, add a CNAME record pointing the subdomain at the *.pages.dev hostname.
  • Cloudflare validates the record, provisions the SSL certificate automatically, and activates the domain — no cert files, no CSRs, no tickets.

A few minutes after the CNAME landed, the site was serving over HTTPS on the client's subdomain. Total elapsed time from here's a PDF to it's live: one working day.

Five takeaways for agencies shipping fast

  • Put content in data, not markup. Typed data files plus dynamic-route templates meant 14 detail pages cost barely more than one — and copy edits never risk breaking layout.
  • Tokenize colors from the first commit. CSS custom properties turned a full mid-project re-theme into a 30-minute, one-file change.
  • You don't need React to get Framer Motion. The motion vanilla API delivers spring physics in an Astro site that still ships almost no JavaScript.
  • Treat animation as progressive enhancement. Respect prefers-reduced-motion, and make sure the page is complete before a single script runs.
  • Generate structured data from the same source as your copy. Schema that's derived from your content data can't fall out of sync with the page.

Modern logistics website design doesn't have to mean a six-week engagement — the right architecture compresses timelines without compressing quality. If you've got a launch that can't wait (even if all you have is a PDF and a logo), get in touch — we'd love to hear about it.

Frequently asked questions

For a content-driven marketing site with a clear copy source, yes. The enablers are architectural: page copy stored in typed data files, dynamic-route templates that render many pages from one layout, and a static-first framework like Astro. Custom web apps, e-commerce builds, and sites needing new copywriting take longer.

Astro renders everything to static HTML and ships zero JavaScript by default, so marketing pages load fast with no tuning. It suits sites that are mostly content with islands of interactivity. For stores or apps with heavy dynamic behavior, a fuller framework or platform is usually the better fit.

Yes. The motion npm package exposes Framer Motion's animation engine through a vanilla JavaScript API — animate(), inView(), stagger(), and spring physics all work in plain script tags with no React runtime.

At minimum: Organization, WebSite, BreadcrumbList on every page, Service schema on each service page, and FAQPage wherever real questions are answered. Generating the JSON-LD from the same data files that render the visible copy keeps the schema from drifting out of sync.

Yes. Add the hostname as a custom domain on the Pages project, then create a CNAME record in the account that owns the DNS zone pointing at the project's pages.dev hostname. Cloudflare validates the record and issues the SSL certificate automatically — no zone migration needed.

Yes. Alongside our Shopify development work, we build fast, SEO-structured marketing sites for service businesses — logistics, professional services, and B2B — using static-first stacks like Astro deployed on Cloudflare. Get in touch through our contact page to scope a build.

Ready to turn more browsers into buyers?

Tell us about your store and goals. We'll map out a plan within one business day.