Hey 👋 As you mentioned, above SSR example repo should let you do it. But to be honest, SSR is very very complex and doesn't give as much benefits.
What you can do is have 3 states: null | false | true
.
If null, show skeleton loaders (or hide fully)
If false, show button X
If true, show button Y
By default you set it to null, and after 1-2s it switches to proper boolean.
Such UI can be seen in many big providers such as DigitalOcean, Railway, or even Facebook. It is much faster solution and user doesn't usually care too much. In the end, those authentication requests are usually very optimized, and in Appwrite they can resolve within <0.1s.
Here is quick showcase of how such client-side-rendered UI looks in Railway:
well right now if the userLoading is true I show the skeleton loeaders and after if user = true show button X and false button Y
But for example, to make private route, how can I do ?
Because we gonna see the private route (like during 0.5s) if we arent connected ?
Or I can make a loader
You can do the same logic, a skeletonal loader instead of the outlet.
Something like (pseudo code):
<Header />
{ isLoading ? <Loader /> : <Outlet /> }
<Footer />
I guess I have to add this condition in all private page right ? I cant use middlesware file (I don't really know middleware but I thought I understood that it was used to make private routes) ?
Maybe middlesware only works on server-side ?
Possibly, I am not too experience with Next.js. Cant you do that typical "use client" in there?
You could also do this in the layout file. That layout would then have array of routes that needs to be protected, so it knows when to check and when not to
(just wild guess from docs), you can do into your layout file and add getData
method where you try to get account, and return something based on that.
Then, you add use client
at the top, and in yoru component you const data = await getData()
With that, loading state should be handeled by Next.js.. Soo maybe white page, maybe pending redirect, not sure. But it should be some out-of-the-box behaviour.
Refference: https://nextjs.org/docs/app/building-your-application/data-fetching/fetching
There is my user.tsx
hook for get Auth (and other stuff like login, signup, etc...) :
"use client"
import React, {createContext, useContext, useState, useEffect } from 'react'
import { account, databases } from '@/utils/appwrite'
export interface UserState {
user: any;
userLoading: boolean;
login: (email: string, password: string) => Promise<void>;
logout: () => Promise<void>;
signup: (email: string, name: string, username: string, password: string) => Promise<void>;
}
const defaultState: UserState = {
user: null,
userLoading: true,
login: async () => {},
logout: async () => {},
signup: async () => {},
}
// create the context
const UserContext = createContext<UserState>(defaultState);
// create the provider component
export const UserProvider = ({ children } : { children: any }) => {
const [ user, setUser ] = useState<null | any>(null);
const [ userLoading, setUserLoading ] = useState(true);
useEffect(() => {
const chekcUser = async () => {
try {
const userRequest = await account.get();
const userDetails = await databases.getDocument(String(process.env.NEXT_PUBLIC_APPWRITE_DATABASE_USERS), String(process.env.NEXT_PUBLIC_APPWRITE_COLLECTION_USER), userRequest.$id)
setUser({
...userRequest,
username: userDetails.username,
avatar: userDetails.avatar,
language: userDetails.language,
})
} catch (error) {
setUser(null)
} finally {
setUserLoading(false)
}
}
chekcUser();
}, [])
const login = async (email: string, password: string) => {
try {
await account.createEmailSession(email, password);
const userRequest = await account.get();
const userDetails = await databases.getDocument(String(process.env.NEXT_PUBLIC_APPWRITE_DATABASE_USERS), String(process.env.NEXT_PUBLIC_APPWRITE_COLLECTION_USER), userRequest.$id)
setUser({
...userRequest,
username: userDetails.username,
avatar: userDetails.avatar,
language: userDetails.language,
})
} catch (error) {
throw error
}
}
const logout = async () => {
try {
await account.deleteSession('current');
setUser(null);
} catch (error) {
throw error
}
}
const signup = async (
email: string,
name: string,
username: string,
password: string
) => {
try {
const {$id} = await account.create('unique()', email, password, name)
await account.createEmailSession(email, password);
await databases.createDocument(String(process.env.NEXT_PUBLIC_APPWRITE_DATABASE_USERS), String(process.env.NEXT_PUBLIC_APPWRITE_COLLECTION_USER),$id, {
"userId": $id,
"username": username
});
await account.createVerification(process.env.NEXT_PUBLIC_URL + '/verifyEmail')
const userRequest = await account.get();
const userDetails = await databases.getDocument(String(process.env.NEXT_PUBLIC_APPWRITE_DATABASE_USERS), String(process.env.NEXT_PUBLIC_APPWRITE_COLLECTION_USER), userRequest.$id)
setUser({
...userRequest,
username: userDetails.username,
avatar: userDetails.avatar,
language: userDetails.language,
})
} catch (error) {
throw error
}
}
return (
<UserContext.Provider value={{ user, userLoading, login, logout, signup }}>
{children}
</UserContext.Provider>
)
}
// create the custom hook
export const useUser = () => useContext(UserContext);
(Sorry for the length, I want to give a max of context :D)
But as I use a context to have the state of the user, in layout.tsx
, the value of user does not update (so false by default) since it is not in the context
Hmm, it should work in layout, I think 🤔
If you do something like const userData = useUser();
and then useEffect(() => { console.log(userData); }, [userData]);
, it should log everytime it changes
Buut I might easily be wrong, I haven't worked with react too much
If above works, from that useEffect you can change some kind of layout state (useState) between true/false so it knows if it should render outlet or not (or even redirect)
Considering this :
export const useUser = () => useContext(UserContext);
I guess useUser() hook update the value of user only in the UserContext
Might be the case. Would there be anything wrogn with wrapping your whole app inside user context? I am thinking about having 2-layer layout.
First one on /*
, where you add user context.
Another one for /dashboard/*
, that uses value from that user context.
This way you put all auth-protected pages under "dashboard" folder, and it will make them all protected by default, thanks to the second layout
U mean the RootLayout create the UserContext like as I currently have and another Layout for private route (like /account, /setting) and public route (for page who cant be seen by logged user like /login /signup) ?
Yes, pretty much.
But for simplicity, I would move /account and /settings to /dashboard/account
/dashboard/settings
. That way you only need to setup that /dashbaord
layout to have protection, and you prevent code duplication
I dont have dashboard page actually but yeah I can find a name for all private route
Name is up to you /user
, /app
.. If they go to that page without going to sub-page, you can redirect.. For example, going to /dashboard
, you can simply send user to /dashboard/account
(always)
its probably a stupid question but I have this problem. I dont wanna show the reset password form if the token and the secret are no available. For example ive got this link :
http://localhost:3000/resetPassword?userId=6491ce764d6de085bc87&secret=a4a063c0f067ef4344ab7ce4ef3e365f64bf3c235e4f03c769c40def255b20f62a499d427f59d6a0805d557ac15d5cb8ad1f5e6d9c167d3067b72f259928b13ddaaada1cf45363c09b1bc9ec1ea3585a398a7855b4a0aa7103b9e4e640510323b77e4068c2dafa967825387745823ac3588f211c14f13acb8843641fac6ed290&expire=2023-06-22+16%3A00%3A17.687
But how check if the token and the secret are available without launch the .updateRecovery()
function
Because even if I the expire
value is on the time, if the token is false, the form will be displayed
Recommended threads
- self-hosted auth: /v1/account 404 on saf...
Project created in React/Next.js, Appwrite version 1.6.0. Authentication works in all browsers except Safari (ios), where an attempt to connect to {endpoint}/v1...
- delete document problems
i don't know what's going on but i get an attribute "tournamentid" not found in the collection when i try to delet the document... but this is just the document...
- Update User Error
```ts const { users, databases } = await createAdminClient(); const session = await getLoggedInUser(); const user = await users.get(session.$id); if (!use...