Troubleshooting Posts
Fix 404 while reloading Gatsby Website for dynamic client-only routeGatsby
Fix 404 while reloading Gatsby Website for dynamic client-only routeGatsby
Last week, we run into a problem for one of the large Gatsby + ReactJS + Laravel projects in hosting which is hosted with Apache Webserver on Amazon AWS EC2. The problem we were facing was, for some reason, when we reload the Gatsby website, it was giving a 404 error page.
If you open a home page and then a normal visit then the website will fully function, but if you reload the page then it gives an error. And we found it happens when we are using Dynamic routing of React Route in Gatsby as per show in Gatsby documentation here.
Also, what we found, if we test the website build with gatsby serve
then it works fine. But while using Apache, it behaves differently and we found that this problem has been faced by lots of people over the internet.
So what we came up with is, we used gatsby serve
with an apache proxy. Here is how we did it,
Step 1 - Setup Project
As a first step, clone the project on the server and run a command, gatsby build
to create a gatsby build.
Step 2 - Setup PM2 for Gatsby Serve
The next step that we need to do is run gatsby serve
. But as you know, we can not run this command directly via console, because as you exit from the console, the command will be terminated.
So we will be using pm2 package, a NodeJS utility that is used to run nodejs apps.
For that, we will need to install pm2 globally. Run the following command to install it,
npm install pm2 -g
You can find other installation ways here if you need.
Once the installation has been done, let's run the gatsby serve command via pm2. For that run the following command from the gatsby project folder,
pm2 start gatsby --name my-web-app -- serve
where my-web-app
you can replace with the name of your app.
Once, it's running, try to test it, if it's working correctly by opening the URL http://your-ip-address:9000/. Make sure, port 9000 is opened on your server for traffic.
Step 3 - Configure Apache
Once, gatsby serve
is working and tested. The next step is to configure apache to proxy all port 80 traffic to port 9000.
For that, edit your apache conf file (or virtual host conf file), and add the following lines (or configure it to something like the following),
<VirtualHost *:80>
ServerName my-web-app.infyom.com
ServerAdmin webmaster@infyom.com
ProxyRequests On
ProxyPass / http://localhost:9000/
ProxyPassReverse / http://localhost:9000/
ErrorLog ${APACHE_LOG_DIR}/my-web-app.infyom.com.error.log
CustomLog ${APACHE_LOG_DIR}/my-web-app.log combined
......
# any other options below as per your need
......
</VirtualHost>
The next step you need to do is restart your apache server by,
sudo service apache2 restart
And then you can just open the URL https://my-web-app.infyom.com and it should work fine.
Bonus
New Deployment
Whenever you deploy a new code, you again need to run gatsby build
and then pm2 restart my-web-app
. Then only it will take new changes.
Troubleshooting
Sometimes, we found that we need to restart apache as well after the new deployment. so if you run into any trouble, then make sure to restart apache as well and it should solve the problem.
I hope it may help you to resolve your 404 problem.
How to use select2 with livewireLaravel
How to use select2 with livewireLaravel
Most of the developers are facing a select2 style removing issue when livewire renders the component.
We can resolve this issue by using a livewire javascript hook.
Here is my screen with select2 before livewire component rendering.
And when the livewire component is refreshed means re-render the select2 style is gone ☹️
How to Fix it ?? 🤔
Well, you just need to add some JQuery code to your livewire component. Here we are going to use afterDomUpdate webhook of livewire. add the following code to your livewire component :
document.addEventListener('livewire:load', function (event) {
window.livewire.hook('afterDomUpdate', () => {
$('#select2ID').select2();
});
});
livewire:load is listening events when the livewire component is loaded and we can add our code within it.
And now when your livewire component is refreshed your select2 style will be still there as we are again applying it.
Other Livewire Posts:
Stay tuned to us for more interesting stuff about livewire.
Use Laravel Debugbar's measure function to optimize application performanceLaravel
Use Laravel Debugbar's measure function to optimize application performanceLaravel
Laravel Debugbar is a great package to debug Laravel applications while developing. But it's not just limited to debugging. You can use it to optimize the performance of your app a lot as well. Like,
- number of models loaded in a memory
- number of queries fired with timing
- memory used and more.
In short, we can have a complete view of what's going on in each request.
But the less known and used feature it gives is the Timeline tab. Where you can see how much time is taken for each request. And more than that, how much time Laravel took to boot up and how much time our other code has taken. Check the below screenshot.
Recently we came to the case, where one of our consultation clients' CRM application was taking too much time on the first load. Their developers were not able to spot a problem. They checked queries and other stuff which looked completely good. so we were not sure where the time had been spent by the application.
That's where debugbar came to rescue us. We used its measure function facility by which we can measure the time spent in each of the functions wherever we want to use. It gives simply two functions startMeasure
and stopMeasure
to measure the time spent between these two statements.
so we can put startMeasure
in the starting of function and put stopMeasure
at the end of the function which will render something like this in the timeline tab.
public function searchClients($department)
{
\Debugbar::startMeasure("searchClients'');
// logic here
\Debugbar::stopMeasure("searchClients");
return $result;
}
Once we put this, we get a time that < code > searchClients is taking. Check the screenshot below,
Hope this can help you to figure out what piece of code is taking the time and you can optimize it.
Happy Optimizing :)
How to remove public path from URL in Laravel ApplicationLaravel
How to remove public path from URL in Laravel ApplicationLaravel
While hosting on the Laravel project on cPanel, the traditional problem that lots of developers get is, /public
path is appended to the URL. Because in most cases, we put a project directly into the public_html
folder, so public_html
is our root for the website and that's where our laravel application is also placed.
But to run the Laravel application, we need to point our domain root to the public
folder of the laravel. It is possible to do it with cPanel but you need to go through some steps which are not known by most of the people and also the tedious process. So to make it simple, what you can do is, there is a way we can do it via the .htaccess
file in our root folder.
We can copy the .htaccess
file from our public
folder and then make modifications to it to work with the direct root folder and route every request to the public
folder.
Here is the final .htaccess
file,
<IfModule mod_rewrite.c> <IfModule mod_negotiation.c> Options -MultiViews -Indexes </IfModule>
RewriteEngine On
# Handle Authorization MemberHeader
RewriteCond %{HTTP:Authorization} .
RewriteRule .* - [E=HTTP_AUTHORIZATION:%{HTTP:Authorization}]
# Redirect Trailing Slashes If Not A Folder...
RewriteCond %{REQUEST_FILENAME} !-d
RewriteCond %{REQUEST_URI} (.+)/$
RewriteRule ^ %1 [L,R=301]
# Remove public URL from the path
RewriteCond %{REQUEST_URI} !^/public/
RewriteRule ^(.*)$ /public/$1 [L,QSA]
# Handle Front Controller...
RewriteCond %{REQUEST_FILENAME} !-d
RewriteCond %{REQUEST_FILENAME} !-f
RewriteRule ^ index.php [L]
By adding the above file to the root folder we can use laravel projects without the public path. Check out the following two lines:
RewriteCond %{REQUEST_URI} !^/public/ RewriteRule ^(.*)$ /public/$1 [L,QSA]
These two lines make magic and our application will work without public
path in URL.
Custom path generation in Spatie Media Library while multi-tenantLaravel
Custom path generation in Spatie Media Library while multi-tenantLaravel
Recently we use Spatie laravel-multitenancy package in our of our client's CRM project along with spatie/laravel-medialibrary.
The default behavior of the media library package is, it generates a folder for each media with its ID in our configured disk, like a public folder or s3 or whatever disk we configured.
This works really great when you are dealing with a single-tenant application. But while using multi-tenant, you got a media table in each of your tenant databases. so this pattern simply doesn't work. Because then you will end up having multiple media files under the same folder from different tenants.
So what we want to do is, instead of having a structure like,
public -- media
---- 1
------ file.jpg
---- 2
------ file.jpg
...
What we want to achieve is, we want to have a folder structure where media of every tenant will be in a separate folder with tenant unique id,
public -- media
---- abcd1234
// tenant1 Id
------ 1
-------- file.jpg
------ 2
-------- file.jpg
---- efgh5678
// tenant2 Id
------ 1
-------- file.jpg
------ 2
-------- file.jpg
...
Spatie Media library is very configurable out of the box where you can write your own media library path generator. That is documented very well over here
So what we did is, we wrote our own Path Generator, which can store files into tenants folder. Here is how it looks like,
<?php
namespace App\MediaLibrary;
use Spatie\MediaLibrary\MediaCollections\Models\Media;
use Spatie\MediaLibrary\Support\PathGenerator\DefaultPathGenerator;
class InfyCRMMediaPathGenerator extends DefaultPathGenerator
{
/*
Get a unique base path for the given media.
/
protected function getBasePath(Media $media): string
{
$currentTenant = app('currentTenant');
return $currentTenant->unique_id.DIRECTORY_SEPARATOR.$media->getKey();
}
}
What we did here is, we just simply extended the Spatie\MediaLibrary\Support\PathGenerator\DefaultPathGenerator
of Media Library and override the function getBasePath
. We attached the prefix of the tenant's unique_id
to the path. so instead of 1/file.png
, it will return abcd1234/1/file.png
.
All you need to make sure is, whenever you are uploading a media, your application should be tenant aware. Otherwise, it will not able to get the current tenant.
Hope this helps while using spatie media library along with spatie multi-tenant.
Even if you are not using a spatie multi-tenant, still you can create your own PathGenerator and use it your own way to have a media structure you want.
DateTimeLocal with LaravelCollective Model BindingLaravel
DateTimeLocal with LaravelCollective Model BindingLaravel
Last week, we were working on one project where we were using LaravelCollective for generating our form. LaravelCollective is a really awesome package and reduces lots of efforts, specifically for automatically binding old inputs to our forms.
Problem
With LaravelColletive when we pass null
as a second value, it tried to get old inputs if available and inject them. But for some reason, it was not working with datetimelocal
.
datetimelocal
need a date in Y-m-d\TH:i
Format. When I went into the code of FormBuilder.php it’s already managing that and tries to convert date into that format if you have passed DateTime
object.
So it was completely working fine while creating a record when you do not have any value.
But I have the same form which was used at both the time of Create and Update. And I was passing null
into the value field at both of the time and LaravelCollective injects it automatically from model or old inputs if there is some error. Something like the following,
<div class="form-group col-sm-6">
{!! Form::label('due_date', 'Due Date:') !!}
{!! Form::datetimeLocal('due_date', null, ['class' => 'form-control']) !!}
</div>
So, Due date will be automatically placed from the model. It’s working fine with all other fields except datetimelocal
.
Solution
The reason behind that is, the value is retrieved from model due_date field, but it comes in Carbon instance and when it converts to a date string, it’s converted into default format which is Y-m-d h:i:s
. So it will not work for datetimelocal
input since it requires Y-m-d\TH:i
format.
So as a solution, what change we did is, instead of passing null
value, we first check, if the model is there then pass the value directly to the input. Something like,
<div class="form-group col-sm-6">
{!! Form::label('due_date', 'Due Date:') !!}
{!! Form::datetimeLocal('due_date', (isset($task)) ? $task->due_date : null, ['class' => 'form-control']) !!}
</div>
So, I will check if I have passed the model $task
to the view and then I will pass a due_date value to input. So FormBuilder will convert it to the proper format and it will get displayed into an input.
Now, when we save the form, it will also return the date into Y-m-d\TH:i
format, so again we need to convert it to the proper format. For that, we created a mutate attribute for due_date
in my Task Model.
public function setDueDateAttribute($value)
{
$this->attributes['due_date'] = Carbon::parse($value);
}
And that’s it. Our datetimelocal
input gets working. I have seen lots of issues on stackoverflow for it. So hope it may help someone.