Concepts, value, and typical clone scenarios — less code.
Concepts, value, and typical clone scenarios — less code.
Preparing Ring content
Preparing Ring content
Preparing Ring content
URL change: member articles now live at /{username}/{slug}, next to the member's profile at /{username}. The older /blog/{username}/{slug} pattern is gone and there is no /blog route. Use the Founder / Developer tabs in the sidebar to filter this page.
Member blogs turn your clone's news module into a publishing space for every member. Articles are stored in the same news table as official news, tagged with the author's handle, and served on the author's own URL. If an author wants the main feed, they request promotion and the article enters the News Kingdom pipeline.
| Earlier docs said | Current behavior |
|---|---|
/blog/{username} and /blog/{username}/{slug} | /{username} (profile) and /{username}/{slug} (article) |
| Paid membership unlocks the blog | Writing needs role member or higher. A paid membership upgrade grants member |
| Profile shows a Blog link and a Telegram badge | Not in the current profile page. Authors share their link from /my-news |
| Without membership, the blog route returns 404 | An article returns 404 when it is not published, the handle or slug does not match, or the locale differs |
No. There is no /blog route or redirect in ring/web. Update shared links to /{username}/{slug}.
Favorites run through GET and POST /api/user/favorites with the useContentFavorite hook. That hook is wired into the news article view (components/wrappers/news-article-wrapper.tsx), not into the /{username}/{slug} page.
Next-step: what happens after an author requests main-page promotion.
Prerequisite: the news module that stores and renders member articles.
Same-workflow: the /{username} page that shares the member's handle.
Depends-on: how handles are chosen and protected, which decides blog URLs.
URL change: member articles now live at /{username}/{slug}, next to the member's profile at /{username}. The older /blog/{username}/{slug} pattern is gone and there is no /blog route. Use the Founder / Developer tabs in the sidebar to filter this page.
Member blogs turn your clone's news module into a publishing space for every member. Articles are stored in the same news table as official news, tagged with the author's handle, and served on the author's own URL. If an author wants the main feed, they request promotion and the article enters the News Kingdom pipeline.
| Earlier docs said | Current behavior |
|---|---|
/blog/{username} and /blog/{username}/{slug} | /{username} (profile) and /{username}/{slug} (article) |
| Paid membership unlocks the blog | Writing needs role member or higher. A paid membership upgrade grants member |
| Profile shows a Blog link and a Telegram badge | Not in the current profile page. Authors share their link from /my-news |
| Without membership, the blog route returns 404 | An article returns 404 when it is not published, the handle or slug does not match, or the locale differs |
No. There is no /blog route or redirect in ring/web. Update shared links to /{username}/{slug}.
Favorites run through GET and POST /api/user/favorites with the useContentFavorite hook. That hook is wired into the news article view (components/wrappers/news-article-wrapper.tsx), not into the /{username}/{slug} page.
Next-step: what happens after an author requests main-page promotion.
Prerequisite: the news module that stores and renders member articles.
Same-workflow: the /{username} page that shares the member's handle.
Depends-on: how handles are chosen and protected, which decides blog URLs.
URL change: member articles now live at /{username}/{slug}, next to the member's profile at /{username}. The older /blog/{username}/{slug} pattern is gone and there is no /blog route. Use the Founder / Developer tabs in the sidebar to filter this page.
Member blogs turn your clone's news module into a publishing space for every member. Articles are stored in the same news table as official news, tagged with the author's handle, and served on the author's own URL. If an author wants the main feed, they request promotion and the article enters the News Kingdom pipeline.
| Earlier docs said | Current behavior |
|---|---|
/blog/{username} and /blog/{username}/{slug} | /{username} (profile) and /{username}/{slug} (article) |
| Paid membership unlocks the blog | Writing needs role member or higher. A paid membership upgrade grants member |
| Profile shows a Blog link and a Telegram badge | Not in the current profile page. Authors share their link from /my-news |
| Without membership, the blog route returns 404 | An article returns 404 when it is not published, the handle or slug does not match, or the locale differs |
No. There is no /blog route or redirect in ring/web. Update shared links to /{username}/{slug}.
Favorites run through GET and POST /api/user/favorites with the useContentFavorite hook. That hook is wired into the news article view (components/wrappers/news-article-wrapper.tsx), not into the /{username}/{slug} page.
Next-step: what happens after an author requests main-page promotion.
Prerequisite: the news module that stores and renders member articles.
Same-workflow: the /{username} page that shares the member's handle.
Depends-on: how handles are chosen and protected, which decides blog URLs.
Handles decide blog URLs, so reserved names matter.
| Surface | Path |
|---|---|
| Write a new article | /news/create |
| Manage, share and promote | /my-news |
| Public article | /{username}/{slug} |
| All articles by one author | /news/author/{username} |
lib/blog/blog-path.tsnormalizeBlogHandleprofileArticlePathnameprofileArticleHref| Absolute URL | lib/site-branding.ts localeBlogArticleUrl(locale, username, slug) |
| Save action | app/_actions/news.ts |
| Permissions | features/news/lib/news-permissions.ts |
The article page looks up one row in news that matches all four filters: blogUsername == normalizeBlogHandle(username), slug, locale and status == 'published'. If no row matches, it calls notFound() and the metadata falls back to noindex. Handles are stored with a leading @, so /en/alice/hello looks for blogUsername: '@alice'.
blogUsername is stamped only on create, and only when the session user has a username. Articles written before the author picked a username never get a handle, so they will not resolve at /{username}/{slug}. The /my-news rail shows a set-username prompt for this case.
Static routes such as /store, /news or /about take priority over the dynamic [username] segment. A username that matches a static route cannot own a profile URL.
| Action | Rule |
|---|---|
| Create | canCreateNewsArticle: role member or higher, platform admin, or confidential |
| Edit or delete | Platform admin or the article author |
Visibility site-wide | Platform admins only. Members reach it through promotion approval |
Visibility confidential | Users with confidential access only |
featured | Forced to false for non-admins on save |
| Propose a revision | Signed-in non-author with canProposeRevision, on published articles |
Create
A member opens /news/create. On save, app/_actions/news.ts writes the article through createNewsArticle() with blogUsername and records the first version commit (lib/versioning).
Publish and share
Set status: 'published'. The /my-news rail builds the public link from ROUTES.PUBLIC_PROFILE(handle) and offers a copy-link button.
Promote (optional)
features/news/components/news-promotion-panel.tsx calls POST /api/news/promotion/submit with { articleId }. Only the author may submit. See News Kingdom for scoring, payment and approval.
Each article view is counted by recordArticlePageView() inside after(), so the count never blocks rendering.
See-also: product offers and author commission on member posts.
Handles decide blog URLs, so reserved names matter.
| Surface | Path |
|---|---|
| Write a new article | /news/create |
| Manage, share and promote | /my-news |
| Public article | /{username}/{slug} |
| All articles by one author | /news/author/{username} |
lib/blog/blog-path.tsnormalizeBlogHandleprofileArticlePathnameprofileArticleHref| Absolute URL | lib/site-branding.ts localeBlogArticleUrl(locale, username, slug) |
| Save action | app/_actions/news.ts |
| Permissions | features/news/lib/news-permissions.ts |
The article page looks up one row in news that matches all four filters: blogUsername == normalizeBlogHandle(username), slug, locale and status == 'published'. If no row matches, it calls notFound() and the metadata falls back to noindex. Handles are stored with a leading @, so /en/alice/hello looks for blogUsername: '@alice'.
blogUsername is stamped only on create, and only when the session user has a username. Articles written before the author picked a username never get a handle, so they will not resolve at /{username}/{slug}. The /my-news rail shows a set-username prompt for this case.
Static routes such as /store, /news or /about take priority over the dynamic [username] segment. A username that matches a static route cannot own a profile URL.
| Action | Rule |
|---|---|
| Create | canCreateNewsArticle: role member or higher, platform admin, or confidential |
| Edit or delete | Platform admin or the article author |
Visibility site-wide | Platform admins only. Members reach it through promotion approval |
Visibility confidential | Users with confidential access only |
featured | Forced to false for non-admins on save |
| Propose a revision | Signed-in non-author with canProposeRevision, on published articles |
Create
A member opens /news/create. On save, app/_actions/news.ts writes the article through createNewsArticle() with blogUsername and records the first version commit (lib/versioning).
Publish and share
Set status: 'published'. The /my-news rail builds the public link from ROUTES.PUBLIC_PROFILE(handle) and offers a copy-link button.
Promote (optional)
features/news/components/news-promotion-panel.tsx calls POST /api/news/promotion/submit with { articleId }. Only the author may submit. See News Kingdom for scoring, payment and approval.
Each article view is counted by recordArticlePageView() inside after(), so the count never blocks rendering.
See-also: product offers and author commission on member posts.
Handles decide blog URLs, so reserved names matter.
| Surface | Path |
|---|---|
| Write a new article | /news/create |
| Manage, share and promote | /my-news |
| Public article | /{username}/{slug} |
| All articles by one author | /news/author/{username} |
lib/blog/blog-path.tsnormalizeBlogHandleprofileArticlePathnameprofileArticleHref| Absolute URL | lib/site-branding.ts localeBlogArticleUrl(locale, username, slug) |
| Save action | app/_actions/news.ts |
| Permissions | features/news/lib/news-permissions.ts |
The article page looks up one row in news that matches all four filters: blogUsername == normalizeBlogHandle(username), slug, locale and status == 'published'. If no row matches, it calls notFound() and the metadata falls back to noindex. Handles are stored with a leading @, so /en/alice/hello looks for blogUsername: '@alice'.
blogUsername is stamped only on create, and only when the session user has a username. Articles written before the author picked a username never get a handle, so they will not resolve at /{username}/{slug}. The /my-news rail shows a set-username prompt for this case.
Static routes such as /store, /news or /about take priority over the dynamic [username] segment. A username that matches a static route cannot own a profile URL.
| Action | Rule |
|---|---|
| Create | canCreateNewsArticle: role member or higher, platform admin, or confidential |
| Edit or delete | Platform admin or the article author |
Visibility site-wide | Platform admins only. Members reach it through promotion approval |
Visibility confidential | Users with confidential access only |
featured | Forced to false for non-admins on save |
| Propose a revision | Signed-in non-author with canProposeRevision, on published articles |
Create
A member opens /news/create. On save, app/_actions/news.ts writes the article through createNewsArticle() with blogUsername and records the first version commit (lib/versioning).
Publish and share
Set status: 'published'. The /my-news rail builds the public link from ROUTES.PUBLIC_PROFILE(handle) and offers a copy-link button.
Promote (optional)
features/news/components/news-promotion-panel.tsx calls POST /api/news/promotion/submit with { articleId }. Only the author may submit. See News Kingdom for scoring, payment and approval.
Each article view is counted by recordArticlePageView() inside after(), so the count never blocks rendering.
See-also: product offers and author commission on member posts.