south-admin-react is a Vite and Antd admin shell with routes generated from the folder tree
React-Admin后台管理系统模版,React19、Vite7、Antd5、Unocss、Zustand、Keepalive、i18n国际化、动态菜单、适配手机端、虚拟表格组件等。
At a glance
- What is it?
- south-admin-react is a TypeScript admin template built on React, Vite, Ant Design, UnoCSS and Zustand, where routing, menus, permissions and API calls are derived from the src/pages layout rather than declared by hand. It ships as a pnpm workspace with four matching backends, and it needs its demo content removed before real work starts.
- Who is it for?
- Adopt south-admin-react if you want to skip building the boring half of an admin console, because the folder-based routing, the menu and permission wiring and the workspace layout for shared packages are already consistent, and the init:project script removes the demo pages and their imports in one step.
- Can I use it commercially?
- Yes. MIT is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
- Is it still maintained?
- Yes. The repository last received commits 33 days ago.
- What is it written in?
- Mainly TypeScript, according to GitHub's language statistics.
Answers come from the project's GitHub data, last synced on October 23, 2026, and from our analysis. They are not legal advice.
Editorial analysis
Routing generated from the pages folder, and the filenames that opt out
The most distinctive decision in this template is that routes are derived from the folder structure rather than declared. The README states that routes are generated automatically from the folder path, and that you can change the generation rules in src/router/utils/config.ts.
The opt-out list is what makes this workable. A path containing any of these file or folder names does not become a route: login, forget, components, utils, lib, hooks, model.tsx and 404.tsx. That list is doing specific jobs. login and forget are entry screens that are routed explicitly rather than discovered. components, utils, lib and hooks are the directories that conventionally hold non-page code, so excluding them keeps helper modules from being turned into navigable screens. model.tsx and 404.tsx are the two special cases where the file is a page but not a menu entry, the first because it holds the page's data layer, the second because the not-found page is routed on its own.
So a developer adds a page by creating a folder under src/pages, and the router picks it up. That is a real reduction in boilerplate compared with a template that asks you to register every route in a central array, and it is also a constraint: the URL structure, the component name and the menu position are all consequences of where you put the file, so a page whose path needs to differ from its folder name requires a configuration change rather than a local one.
The repository layout shows the whole shape. There are src/pages with content and system subtrees, src/servers for API interfaces, src/stores for state, src/menus for menu configuration, src/locales for internationalisation, src/router, src/layouts with its own components subfolder, src/components with Form and Table folders, src/assets, src/utils, and a types directory at the root. On top of that sit build/ with vite configuration, packages/ for the monorepo subpackages, public/, index.html, vite.config.ts, tsconfig.json, and eslint.config.js alongside a .eslintrc.cjs.
Permission model: two endpoints and a rule string on the menu
The access control story is compact enough to hold in your head, and it is worth understanding because it is where most of the integration work with your own backend lands.
There are three calls. A login endpoint at /user/login or a refresh endpoint at /user/refresh-permissions returns the user's permissions. A menu endpoint at /menu/list returns menu data, and the data carries a rule field. The README is precise about the fallback: if no rule is returned, the menu item is always shown. So a menu with no rule is public, and a menu with a rule is filtered against the user's permissions.
Inside a page, the check is separate from the menu. The README points at src/pages/system/menu.index.tsx and its pagePermission data, where pagePermission.page is the permission required to display the page, matched against the permissions returned by the first call.
That split is deliberate in a way worth naming. Menu visibility and page access are two different decisions, and a template that conflates them tends to produce interfaces where a hidden menu still has a reachable URL, or where hiding a menu entry locks a user out of a page they legitimately need. Here the menu rule controls what appears in the sidebar and the pagePermission value controls the page, and the same permission list feeds both.
The consequence for an adopter is that the backend has to return the right shapes. permissions on login, rule on menu items, and matching identifiers in the page's pagePermission map. The template supplies the front half of that contract; the back half is yours to build, and the four companion server repositories in the README are the ones that implement it.
Dynamic menus with a static escape hatch in useCommonStore
Menus come from one of two sources, and the README describes both.
The dynamic route fetches the menu tree from the /menu/list endpoint, which is what an admin panel with server-driven navigation wants. The static route is for teams who would rather keep the menu in code.
The static path has a wrinkle that trips people up. The instruction is to change the menuList in useCommonStore inside src/hooks/useCommonStore.ts to defaultMenus, and the snippet shows the shape:
// src/hooks/useCommonStore.ts
import { defaultMenus } from '@/menus';
// const menuList = useMenuStore(state => state.menuList);
const menuList = defaultMenus;The commented-out line is the important detail rather than an accident. The default implementation reads the menu from the Zustand store, populated by the API response. Replacing it with defaultMenus swaps the store read for a static import, so the menus in src/menus become the navigation instead of the server response. The file paths make the shape clear: src/menus holds the menu definitions and src/menus/index.ts exports them as defaultMenus.
The static menus are not only a convenience. The cleanup script's scope table lists the static menu file src/menus/demo.ts and the demo references inside src/menus/index.ts as things the init:project script removes, so the demo menu entries are a separate file rather than mixed into the real ones. That separation is what lets the script delete demo navigation without touching your own.
One detail to watch when you pick the static route: the permission rule on each menu entry still applies to the static list, so a static menu with a rule is filtered against the same permissions the server returned at login. The static list changes where the menu comes from, not how it is gated.
The pnpm workspace, the four workspace packages, and the pnpm 10 postinstall change
The package is set up as a pnpm workspace, with a pnpm-workspace.yaml at the root and a packages directory holding the shared modules: message for the message component, request for the request module, stylelintConfig and utils. The README answers the question of where the @south/xxx dependencies come from by pointing at the packages directory in the root, where you edit the source of each package directly. The three of them appear in the dependency list as workspace:^ entries, which is pnpm's way of pointing at a local package rather than a registry version.
The monorepo layout has a consequence for adding dependencies, and the README calls it out: in a monorepo project, installing a new dependency requires adding -w or --workspace at the end, otherwise the command fails. The example given is pnpm i mobx -w. The install command for the project itself is pnpm install -w for the same reason.
The other monorepo consequence is the prepare hook. The package.json sets prepare to husky install, and the README explains the visible symptom: if commits fail even when you follow the commit convention, run npx husky install in the terminal and try again. The hook is there to enforce the commit message format.
The convention itself follows the vue project's COMMIT_CONVENTION.md, with the familiar prefixes: feat for a new feature, fix for a bug, style for formatting, perf for optimisation, refactor, revert, test, docs, chore for dependency updates and scaffolding changes, workflow, ci, types and wip. The configuration lives in .commitlintrc.json and the hook in .husky/.
Then there is the pnpm 10 and 11 change, which is the most likely thing to cost you an afternoon. The README explains the security mechanism: pnpm v10 and v11 disable dependency postinstall and other build scripts by default, to prevent malicious code, and you run pnpm approve-builds to allow them. Without that step, a dependency that needs a postinstall step silently does not build. Two other install notes cover the more common failure: if pnpm install fails, the README suggests using a proxy, installing with yarn, or switching to the mirror registry with pnpm config set registry https://registry.npmmirror.com.
init:project: removing the demo content and its dangling imports
A template that ships demo pages has an obvious problem: the demo code stays in your repository forever because removing it by hand leaves import statements pointing at files that no longer exist. This project addresses that with a cleanup script and an explicit scope table.
The recommended route is a pnpm script, which the package.json maps to node .claude/skills/init-project/scripts/init.mjs:
pnpm init:projectIt runs a dry-run preview first, showing the files it will delete and the references it will modify, and only then performs the deletion after confirmation. You can pass the mode yourself, with --dry-run to only preview and --yes to skip the confirmation and execute directly.
The scope is defined precisely, and the list is the reason to trust the script. Pages: src/pages/demo/ and src/pages/content/. Components: src/components/Github/. Interfaces: src/servers/content/. Static menus: src/menus/demo.ts plus the demo references in src/menus/index.ts. Internationalisation: src/locales/zh/content.ts and src/locales/en/content.ts. Code references: the Github reference in src/layouts/components/Header.tsx.
The README states that the cleanup also removes the corresponding import statements and JSX references in the code, avoiding dangling references, and that the operation is not reversible, which is why it suggests running it in a clean git working tree so you can roll back.
There are two other ways to run the same thing. A Claude Code skill, where you execute /init-project in Claude Code and the skill calls the same cleanup script with an interactive confirmation. And running the script directly with Python, which the README gives as a preview invocation and an execution invocation against .claude/skills/init-project/scripts/cleanup.py.
After the cleanup, the README says to run pnpm lint and pnpm dev to confirm the project still works. That step matters, since a leftover reference in a file the script did not know about will show up as a lint error rather than a silent problem.
The wider point is the tooling philosophy. There is a Claude Code skill for generating a demo page from a /demo-create command, VS Code snippets for demoPage, demoModel and demoApi in .vscode/south.code-snippets, and a second skill for cleanup. The template treats the agent-assisted workflow as a first-class path rather than a side note.
Where south-admin-react is the wrong choice
Three limits, and the first one is about what you get and what you do not.
The first is that this is a front end only, and the back end is four separate repositories. The README lists south-admin-gin, south-admin-fastApi, south-admin-django and south-admin-nest as the Go, Python, Python and Nest server implementations. They are linked as badges, they are separate projects, and they are not versioned together with this one. If your backend already exists, or if your team standardises on one server framework that is not among those four, you are adopting the front end and writing the API contract yourself. The permission model above tells you the shape of that contract, which is useful, but it does not remove the work.
The second is the dependency weight. The manifest lists Ant Design 6, axios, dayjs, echarts, i18next with a browser language detector and an HTTP backend, keepalive-for-react, lodash, motion, crypto-js, the Iconify icon sets, the dnd-kit drag and drop packages and the wangeditor rich text editor. Every one of these is a real dependency with a real upgrade path, and the project is a monorepo with its own build tooling on top of Vite, including ESLint, stylelint, Prettier, husky and commitlint. A team that wants a very small bundle will spend real effort removing things rather than adding them.
The third is the version drift inside the project itself. The repository description lists React 19, Vite 7 and Antd 5, while the package.json lists antd at ^6.1.3. A description that disagrees with the manifest is a small thing, but in a template it matters, because the template's own components are written against one version of a component library and the Ant Design 5 to 6 jump is exactly the kind of change that breaks those components. Check the manifest rather than the description.
The alternative approach is to build the console on top of a heavier, opinionated framework with its own routing, layout and data layer, where the trade is that you accept the framework's conventions wholesale instead of a set of conventions you can read in one sitting. The difference in approach is that south-admin-react gives you readable source, a flat folder structure and no runtime dependency beyond what you see in package.json, and it asks you to supply the parts it deliberately leaves out, which for many teams is a good trade and for others is a large amount of assembly work spread over the first month.
Editorial conclusion
Adopt south-admin-react if you want to skip building the boring half of an admin console, because the folder-based routing, the menu and permission wiring and the workspace layout for shared packages are already consistent, and the init:project script removes the demo pages and their imports in one step. Do not adopt it if you need a backend included, because the four companion server repositories are separate projects with their own release cycles, and do not expect the Ant Design version in the description to match the one installed. Verify first that the version of antd in package.json is the one you want, since the manifest lists ^6.1.3 while the project description says Antd5, that pnpm approve-builds has been run before the first dev server, since pnpm 10 and 11 block dependency postinstall scripts by default, and that the permission endpoints your backend exposes match the /user/login, /user/refresh-permissions and /menu/list paths the frontend expects.
Frequently asked questions
How do I install and run south-admin-react?
Clone the repository with git clone https://github.com/southliu/south-admin-react.git, enter the directory with cd south-admin-react, install pnpm globally with npm i -g pnpm, install the dependencies with pnpm install -w, then run pnpm dev. Use pnpm build for a production bundle.
Why does pnpm install fail or the dev server not start on pnpm 10?
pnpm v10 and v11 disable dependency postinstall and other build scripts by default as a security measure, so a dependency that needs to build at install time will not. Run pnpm approve-builds to approve the scripts, and if the install itself fails the README suggests using yarn or switching registry with pnpm config set registry https://registry.npmmirror.com.
How do I remove the demo pages and content from south-admin-react?
Run pnpm init:project, which dry-runs first and then deletes after confirmation, with --dry-run and --yes as options. It removes the demo and content pages, the Github component, the content API module, the demo menu and its locale files, and the corresponding import and JSX references.
How are routes generated in south-admin-react?
Routes are generated automatically from the folder path under src/pages, and paths containing login, forget, components, utils, lib, hooks, model.tsx or 404.tsx are skipped. The generation rules can be changed in src/router/utils/config.ts.
How does south-admin-react handle page permissions and menus?
Permissions come from the /user/login or /user/refresh-permissions endpoint, and menu data from /menu/list, where a rule value gates each menu item and an item with no rule is always shown. Page-level checks use the pagePermission data in src/pages/system/menu.index.tsx, and menus can be made static by setting menuList to defaultMenus in src/hooks/useCommonStore.ts.
Why do I need the -w flag when installing packages in south-admin-react?
The project is a pnpm monorepo whose shared code lives in packages/ and is depended on through workspace:^ references. In a workspace, adding a dependency requires -w or --workspace, otherwise the command fails, and the README's example is pnpm i mobx -w.
Official sources
Add this badge to your README
If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.
[](https://hysenlabs.com/projects/southliu-south-admin-react)Was this useful?