In this chapter, we're gonna go ahead and implement subdomain rewrites. Keep in mind that testing this is quite hard in development and it will differ depending on what operating system you use and your knowledge about the hosts file in your operating system. So I am on Mac OS and I was able to test this locally out of the box without modifying anything at all. I cannot guarantee that you will have the same experience. But lucky for us, we never have to use subdomains locally.
It's only important that we enable that in production. What we're going to focus on in this chapter is implementing that logic, right? Whether you manage to test it locally or not doesn't really matter, because it's production that will be the same both for me and for you. But locally, you might not be able to test this if you have a different operating system than I do. Let's start by confirming that we are on the master branch and that we have merged all of the changes.
And after that you can do bun run dev. In this terminal I have my Stripe webhook. So first thing I'm going to do is go back inside of the dot environment here and I'm going to add next public root domain. And that's going to be localhost 3000, basically without the protocol, like this. And then I'm going to go inside of my utils here.
And what we're going to do here for the generate tenant URL will be the following. Let protocol by default will be HTTPS. But if process.environment, node environment is development, protocol will be HTTP. Why HTTP? Because I run locally on HTTP.
So that's why I will, oops, this is an equal, not comparison. And then what we're going to do is do another check here. So yeah, I'm just trying to find the best way that we can do this. We might even be able to do it in an easier way now that I think of it. Yes, we can actually just do this.
So if we are in development, we can just use this right here. But if we are in production, what we are going to do is we will change this to start with... Let's keep the protocol inside of here. There we go. So protocol, that's the first part.
Then this, and then in here we can get our domain which will be process.environment and then you can just copy this from here like this and put an exclamation point at the end So protocol and then in here we're gonna have our domain like this and you can remove this part for now and you're just going to modify so that we first render the tenant slug and then dot domain like this. So basically in development we don't really care. In development we are fine with using the full tenants URL, but in production, we're going to use the protocol tenant-slug.domain. So this will technically be HTTPS and then Antonio.funroad.com because funroad.com will be written in here as next public root domain. So save it like this for now.
So this is the way I want to implement it. But in order for us to test this locally, I will simply have to modify this a little bit so we can see it in localhost as well. So for now, this won't work in localhost. So if you want to, you can go to localhost here and just to confirm whether this is still working or not. So let me see do I have any product available here inside of this collection.
So I'm logged in as John who has verified the Stripe account. So let me click a test John product with a price of 45 and let's click Save. We should be able to load it now and when I click here let's see what happens if it's still working or not. Looks like it's still working and you can see my URL is completely normal. And when I click on John it should still work.
There we go. So I can now load John. Great. Now that we have this, what we have to do is implement the middleware. So let's go inside of source and we don't have any middleware file so let's create a middleware.ts and now we have to import next request and we have to import next server next response from next server let's first export a config inside of that config a matcher So this is what the matcher will do.
I will copy a comment that I have. So it will match all paths except API, next, static, and all root files inside of public. And the way that the regex, that matcher looks like is this. So it doesn't make sense for me to you know type it along. So you can pause the screen.
Actually I'm going to add it to, if you have access to the source code, you can just copy it. But I'm going to add it to the gist public folder with all the assets. Here you go. So middleware matcher like this. Perhaps I will change this to be some other type of public github where you can find each files individually but for now it's gonna be something like this.
Basically if you don't want to type it you can definitely find it somewhere with the description link. Great, so we have this and now let's go ahead and export default asynchronous function middleware. Accept the request which is a type of next request. Now inside of here first let's extract the URL which comes from request next URL. Then let's go ahead and get the host name which is request headers get host or fall back to an empty string.
So what this is going to be is extracting the host name. So extract the host name, which, for example is Antonio.funroad.com, right? Or John.funroad.com. So that's the host name. Or it can also be .localhost 3000, right?
Something like that. Now let's get the root domain, which is process.environment. And let me just copy the key here. And you can fall back to an empty string here if it doesn't exist. And now we're going to check if hostname ends with openBactex dot and then root domain.
In that case, let's get the tenant slug because now we know the format. So hostname.replace, open backdex.rootdomain and empty string. So basically, since we know that the host name ends in .rootdomain.com, so that means something dot and then funroad.com because funroad.com is root domain. So what we do here is we replace that part with an empty string which leaves us with just a tenant. And then what we do is we return next response, rewrite new URL, open backdates slash tenants, tenants slug, and then join URL dot path name.
And after this, add request dot URL. So basically, we are rewriting this format into our normal tenants, tenants slug and then the rest of the path. Otherwise, let's return nextResponse.next like this. So the middleware should be working its magic now. So this is what I'm going to try.
This is my current URL. I will try going to john.localhost3000. And let's see. There we go. You can see that I have been redirected to John.
Now, I have no idea if you will be able to do the same thing. Because as I said, testing this locally is different for everyone, right? Depending on what operating system you have, depending on what you have in your hosts file, depending on what browser you use, depending on the ports, on a bunch of things. So you can try, maybe it will work first time, maybe I'm being dramatic, right? But now we have to, If it doesn't work for you, I would suggest that you simply watch what's happening on my screen.
In here, you can of course double check that you have written everything correctly in the middleware, right? But there is a chance that it's not going to work for you. So I would still suggest that you follow what I do, because we still have to do some crucial changes here. And one of these changes is the, let's go ahead and go inside of utils. It's this one right here.
So I'm just going to go back here, and I will comment this part out. And instead, we're now going to change the protocol to be let, like this. And then in here, if process.environment, node environment, is equal to development protocol, is HTTP, like this. Again, same mistake for me. There we go.
So we can leave it like this now. And if you go, yeah, this now doesn't work properly anymore. So you have to go manually back to localhost 3000. So I have modified this now. The generate tenant URL will now redirect me by itself.
So this is my current URL. Let me show you. It's this. But if I click here, this is now my URL. John.localhost3000 and then products, blah, blah, blah.
So that's how it redirected me. And you can see that all of the other items are also doing the same thing, john.localhost3000. In here, again, john.localhost3000. So this util is most certainly working now amazing so the reason I want you to use this version is because I don't know if your will be working locally. So if yours isn't working locally, I suggest that you use our previous version, which simply doesn't do this part at all.
And then you can change this to constant, You can basically then remove this part. So only in production, it's going to work on subdomains, right? But locally, this function will simply use the full URL. But for now, I'm going to keep it like this, and instead I will console.log this part out because for me these things are working. So now we have to change some things.
For example, when I click on Fun Road, you can see that I can't go back. So we have to go inside of the footer for the tenant And the link will now be changed specifically to process.environment next public app URL and put an exclamation point at the end here. Let's refresh this part and click here and there we go. Now you can see that it's working. So what I want to do now is the following.
I want to go throughout my app and see all the places that I use next public app URL. One thing that I know already is that I use it in the source modules checkout server. So I want to check every single place that I use this. So the account links ones are completely okay, slash admin. That's perfectly fine.
But I think I use them also in the purchase protected procedure in the checkout. So you can see that in here, I'm using this old tenant redirect, which means that in production, when someone purchases something, They will be redirected to this URL instead of the subdomain. So what I suggest is that we generate the let's do let domain here, and like this. And I'm going to do if process environment is node environment development, in that case, the domain will be exactly like this. So let's mark this inside of these template literals.
Oh my god, I'm trying to remove them. Okay. There we go. Else, domain is going to be the following. Input tenant slug and then it will be dot process dot environment next public root domain and then slash checkout But we actually don't need that part because we are going to append on that part.
So now here, you can just replace this with the domain variable. There we go. So now, if we are in development, we will go to slash tenants. But otherwise, oh, actually, oh, I know what we can use. Why didn't I think of this?
We can just use generate tenant URL and pass in the input tenant slug. I completely forgot that we have this. There we go. This can work in client too. Obviously, we're going to test this.
But I think that this might be the last place where we need to do this. I think everything else doesn't need any changing at all. So what I'm going to do is I'm just going to enable this like this. I will remove this part completely and I will put this into a constant. And I will add a little comment here so you know what's going on.
In development mode, use normal routing. In production, use subdomain routing. In production, normal routing will be available as well. So I'm just doing this to show you that it's not that difficult to implement the rewrite system. And then later, we're going to have to add a wildcard domain in our Vercel deployment, which is going to automatically create these.
It's going to handle these infinite subdomains, right? So right now, nothing should change. Everything should work exactly as it's worked before. But one thing that just crossed my mind is actually this utils file. Yeah, let's modify this to use the process.environment next public app URL.
Why do we need this? Well, we need it specifically in things like our checkout procedures here, because I remembered the domain here would not have the full domain. So when you pass in the URL here, it needs to have the full protocol. So just make sure that you add next public app URL here. So if you try your apps now, I don't think anything should be different.
This issue is from the library button, I think. Nothing too serious. There we go. So everything works just fine. I can query.
Normally, everything is working exactly as it should. I see no issues whatsoever. Great. And now I'm gonna go ahead and merge these changes. So I am satisfied with this.
I will think about this, you know, because this is what what this is now doing is it's making every single time that we use this generate URL, like every time it's in a link, it's a little bit, it's a little bit unoptimized now. So if you want, you can search like this. This may be more precise, but I think we also have cases like this, right? So all of these URLs are now being generated as full protocol URLs, which I'm not sure if it's too optimized. It will definitely work.
I'm just not sure if it's the best practice to do it this way. So perhaps you could add include or maybe full URL, something like this. Yeah, I'm not 100% sure. I'm going to leave it like this, and then we are going to go with this in deployment. And I'm going to monitor the performance and come to a conclusion like that.
So we concluded that testing subdomains is hard in development. We have added the middleware logic to rewrite the tenants to subdomain, and we've modified the generateTenantURL method. One thing we haven't done is proper cookie settings, but we did review the stripe URLs. Let's go ahead and review our cookie settings. So let me try and find where our utils are is that instead of auth here utils generate auth cookie here it is so we're gonna have to modify our cookie now the name is fine the value is fine HTTP only is fine and path is fine, but same site has to be set to none.
Domain has to be set to process.environment, next public root domain. And secure needs to be set to true if we are in production like this so name is fine value is value HTTP only set to true path to a forward slash same site none domain next public root domain which is just a domain without the protocol, and secure only if we are in production. Great. In case you are not using generate-auth-cookie, meaning that you are not using the RPC procedures because I showed you both ways of how you can log in, in that case, you're going to have to modify this inside of your users collection. You can do that here.
I think, not sure if admin cookies, not sure where it is config. Or maybe in the payload config.ts, somewhere here, you have access to, I'm not sure where it is now, but I would recommend using basically these ones because otherwise you won't have the same cookie settings as I do. Okay, I found the settings. So it's inside of auth. So for example, you would go here and you would extend auth cookies and then in here, same site set to none and secure set to blah, blah, blah.
And domain set to blah, blah, blah. So that's how you would do it. In case you're using that kind of cookie. We are not, so I don't have to do it because I handle cookies on my own using the generate out cookie method here. So these things are fine.
Great. So let's go ahead and merge this and I'm going to test it in production. I will research a bit about generate tenant URL. Basically, it bothers me that I feel like we are reducing the speed of our app by assigning this every single time. I feel like there is only one place where we actually need to do that, which is in Stripe, right?
Basically in the checkout procedures right here. I feel like perhaps in Here, we could just extend it like this, and add include. Let me think of a cool name. Well, it actually doesn't matter. The reason it doesn't matter, because in production, we are going to use the full link anyway.
So it doesn't matter if we make it more optimized in development. So this is completely fine. Okay, that's it for our middleware implementation. We now also added the proper cookie settings. So 27 subdomain rewrites.
Unfortunately, we will only be able to test it once we deploy, right? I mean, I tested locally a little bit just to confirm it's not failing, but the true magic of it will be shown in production. So git checkout-b 27 subdomain rewrites, git add, git commit 27 subdomain rewrites, and git push uorigin 27 subdomain rewrites. Let's go ahead and Open a pull request to review our changes here. And let's see what our reviewer has to say.
Perhaps we have some critical bugs that it will notice. So here we have the summary. This pull request updates URL generation and routing for a multi-tenant application. The generateTenantURL function now produces environment-specific URLs, a development URL using an environment variable path and a production URL using a subdomain format. A new middleware is added to rewrite request URLs based on the tenant's host name.
Enhancements are also made to cookie settings in the authentication module, and a Flutter link is updated to use dynamic URL from the environment variables. And in here, we can see two sequence diagrams which describe how our get generate tenant URL method works and also how our middleware now works. In here we have some suggestions mostly to add a fallback to our environment variables. In here, they have a good tip. If same site is set to none in cookie, which is something that's true for us, the secure attributes has to be set to true.
In our case, we only set it to true if process environment is production. I don't think this is too big of an issue because we know that our cookies will behave differently on localhost. So I will test just to confirm that we can still test out on localhost. But what matters to us is that it works in production. And in here, it makes an interesting suggestion to validate the tenant simply so we can avoid any errors.
But I think that we don't have to do it right now. I think it's OK the way it is at the moment. In here it also recommends validating the tenant and also adding some form back to the environment variables. Nevertheless I'm satisfied with these changes so I'm gonna go ahead and merge this. So as always I will just confirm that I have the new branch here, there we go, and we can now go back to the master or main, git pull, origin, and There we go.
We now have a new item in our graph here, amazing. And we can check this off as completed. Amazing, amazing job.