โญ๏ธ Simplest implementation for Vue Server-side Rendering with any Backend

โญ๏ธ Simplest implementation for Vue Server-side Rendering with any Backend

ยท

5 min read

Hey guys,

I recently discovered & successfully implemented server-side rendering using PHP + Laravel & Vue 3. Thought I would note them down here and share them with you guys, so I did ๐Ÿ˜‰

With this simplest way, you will have the up-and-running Server-Side Rendering (SSR) in no time. You can even use any frontend framework as well ๐ŸŒน

I'd call this the Minimalism Server-side Rendering (M-SSR) ๐Ÿ˜Ž

The benefits of Server-side Rendering

We all know the real benefits, critical ones are:

  • SEO improvements: yes, increasing your ranking on Search Engine (SE), helps SE to crawl your site better,...

    • Some said that most SEs support crawling JS sites, but don't fully trust that ๐Ÿฅน.
  • Social Media Preview: when sharing our app's URLs to Facebook, Twitter,... they can crawl our content and show the preview better.

If you are working on internal projects that serve internal users and require authentication,... then you don't need SSR at all ๐Ÿ‘€

How M-SSR works

We only need to care about the first request. That's how SEs and Social Networks work, they access your page and then pick the rendered HTML for information.

If there are any redirects or transitions in the application after that, we don't need to care from the user's perspective.

Just keep your document.title updated and we're all good ๐Ÿ˜Ž

M-SSR Backend implementation

I'm adding the implementation for Laravel & Node x Express. It would be not so diff for other langs & frameworks ๐Ÿ˜Ž

Routes

We need to add the routes to have a 1:1 relationship with our Vue/React apps. For example, I have these routes under my Vue app:

  • Home /

  • Products List /products

  • Product Info /products/:slug

  • Contact us /contact-us

Then I'll add these routes in the backend service:

// web.php
Route::get('/', [HomeController::class, 'index']);
Route::get('/products', [ProductController::class, 'index']);
Route::get('/products/{slug}', [ProductController::class, 'show']);
Route::get('/contact-us', [ContactUsController::class, 'index']);
// index.ts
const routes = Router();

routes.get('/', viewHomePageHandler);
routes.get('/products', viewProductsListPageHandler);
routes.get('/products/:slug', viewSingleProductPageHandler);
routes.get('/contact-us', viewContactUsPageHandler);

Data preparation

Static SEO content

Not a big deal, right? We can go either hard-coded or translated texts or retrieve simple info from the Database.

From our example above, static SEO content pages are Home, Products List, and Contact Us.

class HomeController {
    public function index(): Response 
    {
        $title = 'Home Page - Site Name';
        $description = 'Welcome to Site Name, where you can skyrocket your app';
        $socialPreviewImg = asset('assets/images/home-social-preview.jpg');
        // to be continue...
    }    
}
export const viewProductsListPageHandler = (req, res) => {
    const title = 'Products List - Site Name';
    const description = 'Check out our products list and buy them';
    const socialPreviewImg = 'https://image-here';
    // to be continue...
};

Dynamic SEO content

This will be applied for the single product view page, we'll need to get the product details.

From Laravel, we can use the Model Route Binding feature to quickly access the model, it also handles 404 too (peace)

class ProductController {
    public function view(Product $product): Response
    {
        $title = $product->name . ' - Your Site Name';
        $description = $product->short_description;
        $socialPreviewImg = $product->getSocialImage();
        // to be continue
    }
}

For Express app, I usually use a normal repository to get the data, feel free to use any kind of ORMs or anything that fits you most

export const viewSingleProductPageHandler = async (req, res) => {
    const { slug } = req.params;
    const product = await productRepo.getBySlug(slug);
    if (!product) {
        res.status(404);
        return;
    }        

    const title = product.name + ' - Site Name';
    const description = product.short_description;
    const socialPreviewImg = product.social_image;

    // to be continue
}

Preparing View

Our view is now holding the index.html from Vite. Since it will be rendered from the backend side, we can render anything that we want:

  • meta tags

  • title

  • ld+json

  • ...

// app.blade.php
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8" />
    <link rel="icon" href="/favicon.ico" />
    <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
    <title>{{ $title }}</title>
    <meta property="og:title" content="{{ $title }}" />
    <meta property="og:description" content="{{ $description }}" />
    <meta property="og:type" content="website" />
    <meta property="og:image" content="{{ $socialPreviewImg }}" />
    <!-- feel free to add more tags here -->
    <link rel="stylesheet" href="https://rsms.me/inter/inter.css" />
</head>
<body class="g-sidenav-show g-sidenav-pinned">
    <div id="app"></div>
    @vite('resources/js/app.ts')
    <noscript>
        <p>This site requires JavaScript to work.</p>
    </noscript>
</body>
</html>

For node express: feel free to use any templating engine and follow the above. Some popular ones are HandleBars, underscore,...

Response

From our controller methods, prepare and return the rendered view to the client ๐Ÿ˜Ž

class HomeController extends Controller
{
    public function index(): Response
    {
        $title = 'Home Page - Site Name';
        $description = 'Welcome to Site Name, where you can skyrocket your app';
        $socialPreviewImg = asset('assets/images/home-social-preview.jpg');
        // add more for your case

        return response()->view('app', [
            'title' => $title,
            'description' => $description,
            'socialPreviewImg' => $socialPreviewImg,
        ]);
    }
}

Node app, follow the same approach like above ๐Ÿ˜‰

M-SSR Frontend implementation

No further action or any package is required, minimalism isn't it? ๐Ÿ˜Ž

Testing

Simply run your backend and test the result ๐Ÿ˜Ž

Conclusion

Congrats, you are now using M-SSR for your applications.

Let's summarize the PROs & CONs ๐Ÿฅน

PROs

  • Simplest solution ๐Ÿ”‹

  • Super easy to implement ๐ŸŒน

  • No extra package is needed, use what you already have ๐Ÿ˜Ž

    • Less dependency, happier life
  • Hot-reloading works fine, no need to worry ๐Ÿ”ฅ

CONs

  • Only the first request will have the correct tags, if users go to another page, they still have the same tags

    • It won't be a big problem, just ensure you update the document.title and that's all ๐Ÿ˜Ž
  • Need a simple backend service to serve the first request (BE + FE in the same repo)

    • I actually won't count this as a big CON, we all need to have a backend service for SSR anyway ๐Ÿฅฐ

Consideration

  • For PHP apps, consider using Swoole or RoadRunner for high performance.

Project(s) that I'm using this implementation

Check out DevToolz https://tools.sethphat.dev/ ๐Ÿ˜Ž

The DevToolz Repository: https://github.com/sethsandaru/devtoolz

Final words

Hope this will help you to skyrocket the SEO of your websites, blogs, or whatever you are developing for your end-users ๐ŸŒน

Happy coding ๐Ÿ˜Ž!

ย