artificial intelligence

How we made our new website

How we build our Website with Astro, Strapi, SolidJS and Tailwind CSS

We are excited to announce that our new website (this website you're on right now) has been built using four of the most modern web technologies available today: Astro, Strapi, SolidJS and Tailwind CSS. This combination of tools allows us to build a website that is fast, secure, scalable, and visually appealing.

Tom Wenger
2.2.2024

What is Astro?

Astro is a static site generator (SSG) that allows you to use other Frameworks like React, Vue or what we used for dynamic content: SolidJS. Astro is known for its speed, performance, and flexibility. 

Benefits of using Astro:

  • Performance: Astro websites are typically very fast, as they are served as static files. This can lead to improved SEO and user experience. 
  • SEO: Astro websites are SEO-friendly, as they can be configured to use proper meta tags, titles, and descriptions. 
  • Security: Astro websites are more secure than traditional dynamic websites, as they are not vulnerable to common attack vectors such as SQL injection and cross-site scripting. 
  • Flexibility: Astro is a very flexible SSG, and can be used to build a wide variety of websites, from simple blogs to complex e-commerce sites.

In addition to these benefits, Astro is also easy to use and has a large and active community. 

Have a look at the Astro Website

What is Strapi?

Strapi is an open-source headless content management system (CMS) that allows developers to build APIs for managing content.  It is highly flexible, and can be used to build a wide variety of APIs, from simple blog APIs to complex e-commerce APIs.
Strapi's has a nice plugin system, allowing developers to create custom plugins to extend the functionality of the CMS in any way they need.

The large and active community is an advantage too, which means that there is plenty of support and documentation available.

Check it out on the Strapi Website

What is SolidJS?

To avoid having to write plain Javascript for dynamic and animated content like the navbar, the blog carousel or the blog and project filters, we used SolidJS for this. SolidJS is a small and fast JavaScript library, which allows you to write declarative Javascript for building user interfaces. SolidJS is built with fine-grained reactivity, which means that it only updates the DOM when necessary, this makes SolidJS applications very fast and responsive.

The Solid JS Website

Some Examples

Astro Components with Tailwind

With Astro you can quickly create flexible and reusable UI components. Here is an example of how we implemented a button component for our website.

First, we define the props that can be passed to our component:

---
import clsx from 'clsx';

export interface Props {
    size?: 'auto' | 'small' | 'large';
    variant?: 'primary' | 'secondary';
    styles?: string;
    margin?: boolean;
    href?: string;
    target?: string;
    id?: string;
}

const { size = 'auto', variant = 'primary', styles, margin = true, href, target = '_self', id } = Astro.props as Props;

Then we define style variables (objects) with different properties that correspond to the component props

const baseStyles = clsx('text-white', 'transition-all', 'inline-block', styles && styles);

const sizeStyles = {
    auto: clsx(
        'text-title-small-semibold-16-23',
        'border-[3px]',
        'py-xx-small-12',
        'px-small-24',
        '3xl:text-title-large-semibold-28-42',
        '3xl:py-small-16',
        '3xl:px-medium-32',
        '3xl:border-4',
    ),
    small: clsx('text-title-small-semibold-16-23', 'border-[3px]', 'py-xx-small-12', 'px-small-24'),
    large: clsx('text-title-large-semibold-28-42', 'py-x-small-16', 'px-medium-32', 'border-4'),
};

const variantStyles = {
    primary: clsx(
        'bg-lambda-sky-blue',
        'border-lambda-sky-blue',
        'hover:border-lambda-sky-blue',
        'hover:bg-white',
        'hover:text-lambda-sky-blue',
        'active:text-lambda-dark-blue',
    ),
    secondary: '',
};

So we set different styles depending on which props were passed. And of course for each prop a default is defined so that you can also just use this button component in a standard way.

The markup then looks like this:

<>
    {
        href ? (
            <a
                {...(id && { id })}
                href={href}
                target={target}
                class={clsx(baseStyles, sizeStyles[size], variantStyles[variant], margin && 'mt-small-24')}
            >
                <slot />
            </a>
        ) : (
            <button
                {...(id && { id })}
                class={clsx(baseStyles, sizeStyles[size], variantStyles[variant], margin && 'mt-small-24')}
            >
                <slot />
            </button>
        )
    }
</>

So the button component can be a link (a-tag) or a button (button-tag), depending on whether an href is passed. The id can optionally be supplied, e.g. to provide the button with an event listener later.

Reactive Components with SolidJS

For dynamic content we use SolidJS. This has the advantage that Javascript can be used much more declarative. For example, reactive variables (so-called signals) can be placed directly in the markup and the DOM is automatically updated as soon as the signal variable changes. CSS classes can also be set or removed depending on the state of the signal variable. For example, the Navbar-Component, who is interactive and responsive:

export default function Navbar({ activePage }: { activePage: string | undefined }) {

    const [hasMounted, setHasMounted] = createSignal(false);
    const [headerStyles, setHeaderStyles] = createSignal({});
    const [hideLogoText, setHideLogoText] = createSignal(false);
    const [isMobileMenuVisible, setIsMobileMenuVisible] = createSignal(false);
    const [isMobileMenuRendered, setIsMobileMenuRendered] = createSignal(false);
    const [responsiveMenuStyles, setResponsiveMenuStyles] = createSignal({});

    setHeaderStyles({ ...basicStyles, ...normalHeightStyles });
    setResponsiveMenuStyles({ ...responsiveMenuBasicStyles, ...responsiveMenuClosedStyles });

The headerStyles or responsiveMenuStyles signals contain tailwind classes, which are then reset depending on the scroll behavior or scroll position.

const handleScroll = () => {
    if (isMobileMenuVisible()) {
        return;
    }
    currentScrollPos = window.scrollY;

    if (window.scrollY === 0) {
        setHeaderStyles(s => ({ ...s, ...normalHeightStyles, ...moveInStyles, 'shadow-md': false }));
        setHideLogoText(false);
        return;
    } else {
        setHeaderStyles(s => ({ ...s, 'shadow-md': true }));
    }
    // Scrolling UP
    if (prevScrollPos > currentScrollPos) {
        if (currentScrollPos > NAV_SCROLL_OFF_TRIGGER) {
            setHeaderStyles(s => ({ ...s, ...moveInStyles, ...reducedHeightStyles }));
        }
        if (currentScrollPos < NAV_SCROLL_ON_TRIGGER) {
            setHeaderStyles(s => ({ ...s, ...moveInStyles, ...normalHeightStyles }));
            setHideLogoText(false);
        }
    }

    // Scrolling DOWN
    if (prevScrollPos < currentScrollPos) {
        if (currentScrollPos > NAV_SCROLL_OFF_TRIGGER) {
            setHeaderStyles(s => ({ ...s, ...moveOutStyles }));
            setHideLogoText(true);
        }
    }

    // Update the previous scroll position
    prevScrollPos = currentScrollPos;
};
// _throttle comes from lodash
const throttledHandleScroll = _throttle(handleScroll, 250);
window.addEventListener('scroll', throttledResizeListener);

Dynamic routes

For the blog and projects section, where the pages are created dynamically, we need the dynamic routes function of Astro. This creates a static page for every blog post and for every project that is recorded in Strapi. In Astro, however, only one file with the file name [slug].astro is required. During the build process, an HTML file is then created for each project and the slug (unique name without spaces, saved in Strapi) is used as the file name.

Because all routes must be determined at build time, a dynamic route must export a getStaticPaths() that returns an array of objects with a params property. Each of these objects will generate a corresponding route.

The props object contains values that are then available on the page.

---
export async function getStaticPaths() {
    const getPageData = await import('./getProjectData');
    const projectEntries = await getPageData.getData();

    return projectEntries.map(project => ({
        params: { slug: project.slug },
        props: { project },
    }));
}
---
<Layout>
    <h1>{project.title}</h1>
    ...
</Layout>

Conclusion

To summarize, building our new company website with Astro.build, Strapi CMS, SolidJS and Tailwind was a great experience. It took a lot of work, especially to make sure the website works well on all devices and is easy to use for everyone. The implementation also had a few challenges, but more on that in the next blog.

Looking back, however, we can say that the effort was worth it. Because Astro gives us full control over every HTML element and generates static pages at the end, we can keep the bundle size extremely small, which leads to a very good performance. The Google Lighthouse Test thus gives us a perfect score of 100 on almost every page.

lighthouse test result

Author

Lambda IT Tom

Tom Wenger

Spielt Gitarre und geht gerne an Konzerte.