---
title: "Member Blogs"
description: "Every member with a username gets a personal publishing space at /{username}/{slug}. Role-gated writing, the /my-news workspace, author listing, revision proposals and paid promotion to the main feed through News Kingdom. Migration note for the retired /blog/{username} URLs."
locale: "en"
---
# Member Blogs

> **Info**
> **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](/docs/architecture/news-kingdom.md) 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 |

### For founders

## Why this matters for your clone

- **Members become publishers.** Every paying member gets a personal space with a clean, shareable URL under your domain.
- **One identity.** The same username carries the profile, the articles and the author listing.
- **A content funnel you control.** Good posts can be promoted to your front page for a fee. You approve every one.
- **Readers help improve posts.** Other members can propose revisions. The author decides what to accept.

  
- **[News Kingdom](/docs/architecture/news-kingdom.md)** — Scoring, paid promotion and Telegram approval for the main feed.

  
- **[Public profile](/docs/features/public-profile.md)** — The `/{username}` page that sits next to the member's articles.

  
- **[Username reservation](/docs/features/username-reservation.md)** — Handles decide blog URLs, so reserved names matter.

  
- **[Subscriptions](/docs/features/subscriptions.md)** — The paid path that lifts a user to the `member` role.

### Typical scenarios

1. **Expert community.** Members write long-form posts under their own handle, and the best ones are promoted to the front page.
2. **Cooperative newsletter.** Each producer keeps a blog. The admin team picks stories for the main feed.
3. **Membership incentive.** Free users can read. Writing is a benefit of paid membership.

### Where members work

| 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}` |

### For developers

## Routes and modules (verified in `ring/web`)

| Concern | Path |
|---------|------|
| Article page | `app/[locale]/[username]/[slug]/page.tsx` |
| Profile page | `app/[locale]/[username]/page.tsx` |
| Author listing | `app/[locale]/news/author/[username]/page.tsx` (up to 50 published, via `getMyArticles`) |
| Member editor | `app/[locale]/(protected)/news/create/page.tsx` (`ArticleEditor audience="member"`) |
| Member workspace | `app/[locale]/(protected)/my-news/` with `components/layout/rails/my-news-rail.tsx` |
| Amendments inbox | `/my-news/{articleId}/amendments` (`ROUTES.MY_NEWS_AMENDMENTS`) |
| Handle and path helpers | `lib/blog/blog-path.ts` (`normalizeBlogHandle`, `profileArticlePathname`, `profileArticleHref`) |
| Absolute URL | `lib/site-branding.ts` `localeBlogArticleUrl(locale, username, slug)` |
| Save action | `app/_actions/news.ts` |
| Permissions | `features/news/lib/news-permissions.ts` |

## How an article resolves

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'`.

> **Warning**
> `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.

## Permissions

| 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 |

## Write, publish, promote

**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](/docs/architecture/news-kingdom.md) for scoring, payment and approval.

Each article view is counted by `recordArticlePageView()` inside `after()`, so the count never blocks rendering.

## Frequently asked questions

### Migration

#### Will old /blog links still work?

No. There is no `/blog` route or redirect in `ring/web`. Update shared links to `/{username}/{slug}`.

### Impact

#### Can readers bookmark member articles?

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.

## Known gaps

## Related documentation

  
- [architecture/news-kingdom](/docs/architecture/news-kingdom.md) — Next-step: what happens after an author requests main-page promotion.

  
- [features/news](/docs/features/news.md) — Prerequisite: the news module that stores and renders member articles.

  
- [features/public-profile](/docs/features/public-profile.md) — Same-workflow: the /{username} page that shares the member

  
- [features/username-reservation](/docs/features/username-reservation.md) — Depends-on: how handles are chosen and protected, which decides blog URLs.

  
- [features/curtain](/docs/features/curtain.md) — See-also: product offers and author commission on member posts.
