Static websites with Tempest

Written on 2025-03-14

This post was originally published on the Tempest blog.

Let's say you have a controller that shows blog posts — kind of like the page you're reading now:

final readonly class BlogController
{
    #[Get('/blog')]
    public function index(BlogRepository $repository): View
    {
        $posts = $repository->all();

        return view(__DIR__ . '/blog_index.view.php', posts: $posts);
    }

    #[Get('/blog/{slug}')]
    public function show(string $slug, BlogRepository $repository): Response|View
    {
        $post = $repository->find($slug);

        return view(__DIR__ . '/blog_show.view.php', post: $post);
    }
}

These type of web pages are abundant: they show content that doesn't change based on the user viewing it — static content. Come to think of it, it's kind of inefficient having to boot a whole PHP framework to render exactly the same HTML over and over again with every request.

However, instead of messing around with complex caches in front of dynamic websites, what if you could mark a controller action as a "static page", and be done? That's exactly what Tempest allows you to do:

use Tempest\Router\StaticPage;

final readonly class BlogController
{
    #[StaticPage]
    #[Get('/blog')]
    public function index(BlogRepository $repository): View
    {
        $posts = $repository->all();

        return view(__DIR__ . '/blog_index.view.php', posts: $posts);
    }

    // …
}

And… that's it! Now you only need to run tempest static:generate, and Tempest will convert all controller actions marked with #[StaticPage] to static HTML pages:

~ tempest static:generate

- /blog > /web/tempestphp.com/public/blog/index.html

Done

Hold on though… that's all fine for a page like /blog, but what about /blog/{slug} where you have multiple variants of the same static page based on the blog post's slug?

Well for static pages that rely on data, you'll have to take one more step: use a data provider to let Tempest know what variants of that page are available:

use Tempest\Router\StaticPage;

final readonly class BlogController
{
    // …
    
    #[StaticPage(BlogDataProvider::class)]
    #[Get('/blog/{slug}')]
    public function show(string $slug, BlogRepository $repository): Response|View
    {
        // …
    }
}

The task of such a data provider is to supply Tempest with an array of strings for every variable required on this page. Here's what it looks like:

use Tempest\Router\DataProvider;

final readonly class BlogDataProvider implements DataProvider
{
    public function __construct(
        private BlogRepository $repository,
    ) {}

    public function provide(): Generator
    {
        foreach ($this->repository->all() as $post) {
            yield ['slug' => $post->slug];
        }
    }
}

With that in place, let's rerun tempest static:generate:

~ tempest static:generate

- /blog > /web/tempestphp.com/public/blog/index.html
- /blog/exit-codes-fallacy > /web/tempestphp.com/public/blog/exit-codes-fallacy/index.html
- /blog/unfair-advantage > /web/tempestphp.com/public/blog/unfair-advantage/index.html
- /blog/alpha-2 > /web/tempestphp.com/public/blog/alpha-2/index.html
// …
- /blog/alpha-5 > /web/tempestphp.com/public/blog/alpha-5/index.html
- /blog/static-websites-with-tempest > /web/tempestphp.com/public/blog/static-websites-with-tempest/index.html

Done

And we're done! All static pages are now available as static HTML pages that will be served by your webserver directly instead of having to boot Tempest. Also note that tempest generates index.html files within directories, so most webservers can serve these static pages directly without any additional server configuration required.

On a final note, you can always clean up the generated HTML files by running tempest static:clean:

~ tempest static:clean

- /web/tempestphp.com/public/blog directory removed
- /web/tempestphp.com/public/blog/exit-codes-fallacy directory removed
- /web/tempestphp.com/public/blog/unfair-advantage directory removed
- /web/tempestphp.com/public/blog/alpha-2 directory removed
// …
- /web/tempestphp.com/public/blog/alpha-5 directory removed
- /web/tempestphp.com/public/blog/static-websites-with-tempest directory removed

Done

It's a pretty cool feature that requires minimal effort, but will have a huge impact on your website's performance. If you want more insights into Tempest's static pages, you can head over to the docs to learn more.

Things I wish I knew when I started programming

Things I wish I knew when I started programming cover image

This is my newest book aimed at programmers of any skill level. This book isn't about patterns, principles, or best practices; there's actually barely any code in it. It's about the many things I've learned along the way being a professional programmer, and about the many, many mistakes I made along that way as well. It's what I wish someone would have told me years ago, and I hope it might inspire you.

Read more

Comments

Loading…
No comments yet, be the first!
Noticed a tpyo? You can submit a PR to fix it.
HomeRSSNewsletterDiscord© 2025 stitcher.io