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 ๐
- It won't be a big problem, just ensure you update the
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 ๐!