diff --git a/rhodecode/apps/_base/__init__.py b/rhodecode/apps/_base/__init__.py
new file mode 100644
--- /dev/null
+++ b/rhodecode/apps/_base/__init__.py
@@ -0,0 +1,53 @@
+# -*- coding: utf-8 -*-
+
+# Copyright (C) 2016-2017 RhodeCode GmbH
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License, version 3
+# (only), as published by the Free Software Foundation.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU Affero General Public License
+# along with this program.  If not, see <http://www.gnu.org/licenses/>.
+#
+# This program is dual-licensed. If you wish to learn more about the
+# RhodeCode Enterprise Edition, including its added features, Support services,
+# and proprietary license terms, please see https://rhodecode.com/licenses/
+
+import logging
+from pylons import tmpl_context as c
+
+from rhodecode.lib.utils2 import StrictAttributeDict
+
+log = logging.getLogger(__name__)
+
+
+class TemplateArgs(StrictAttributeDict):
+    pass
+
+
+class BaseAppView(object):
+
+    def __init__(self, context, request):
+        self.request = request
+        self.context = context
+        self.session = request.session
+        self._rhodecode_user = request.user
+
+    def _get_local_tmpl_context(self):
+        return TemplateArgs()
+
+    def _get_template_context(self, tmpl_args):
+
+        for k, v in tmpl_args.items():
+            setattr(c, k, v)
+
+        return {
+            'defaults': {},
+            'errors': {},
+        }
+
diff --git a/rhodecode/apps/user_profile/__init__.py b/rhodecode/apps/user_profile/__init__.py
new file mode 100644
--- /dev/null
+++ b/rhodecode/apps/user_profile/__init__.py
@@ -0,0 +1,28 @@
+# -*- coding: utf-8 -*-
+
+# Copyright (C) 2016-2017 RhodeCode GmbH
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License, version 3
+# (only), as published by the Free Software Foundation.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU Affero General Public License
+# along with this program.  If not, see <http://www.gnu.org/licenses/>.
+#
+# This program is dual-licensed. If you wish to learn more about the
+# RhodeCode Enterprise Edition, including its added features, Support services,
+# and proprietary license terms, please see https://rhodecode.com/licenses/
+
+
+def includeme(config):
+    config.add_route(
+        name='user_profile',
+        pattern='/_profiles/{username}')
+
+    # Scan module for configuration decorators.
+    config.scan()
diff --git a/rhodecode/apps/user_profile/tests/__init__.py b/rhodecode/apps/user_profile/tests/__init__.py
new file mode 100644
diff --git a/rhodecode/apps/user_profile/tests/test_users.py b/rhodecode/apps/user_profile/tests/test_users.py
new file mode 100644
--- /dev/null
+++ b/rhodecode/apps/user_profile/tests/test_users.py
@@ -0,0 +1,75 @@
+# -*- coding: utf-8 -*-
+
+# Copyright (C) 2010-2017 RhodeCode GmbH
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License, version 3
+# (only), as published by the Free Software Foundation.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU Affero General Public License
+# along with this program.  If not, see <http://www.gnu.org/licenses/>.
+#
+# This program is dual-licensed. If you wish to learn more about the
+# RhodeCode Enterprise Edition, including its added features, Support services,
+# and proprietary license terms, please see https://rhodecode.com/licenses/
+
+import pytest
+
+from rhodecode.model.db import User
+from rhodecode.tests import (
+    TestController, TEST_USER_ADMIN_LOGIN, TEST_USER_ADMIN_PASS,
+    TEST_USER_REGULAR_LOGIN, TEST_USER_REGULAR_PASS)
+from rhodecode.tests.fixture import Fixture
+from rhodecode.tests.utils import AssertResponse
+
+fixture = Fixture()
+
+
+def route_path(name, **kwargs):
+    return '/_profiles/{username}'.format(**kwargs)
+
+
+class TestUsersController(TestController):
+
+    def test_user_profile(self, user_util):
+        edit_link_css = '.user-profile .panel-edit'
+        self.log_user(TEST_USER_REGULAR_LOGIN, TEST_USER_REGULAR_PASS)
+        user = user_util.create_user(
+            'test-my-user', password='qweqwe', email='testme@rhodecode.org')
+        username = user.username
+
+        response = self.app.get(route_path('user_profile', username=username))
+        response.mustcontain('testme')
+        response.mustcontain('testme@rhodecode.org')
+        assert_response = AssertResponse(response)
+        assert_response.no_element_exists(edit_link_css)
+
+        # edit should be available to superadmin users
+        self.logout_user()
+        self.log_user(TEST_USER_ADMIN_LOGIN, TEST_USER_ADMIN_PASS)
+        response = self.app.get(route_path('user_profile', username=username))
+        assert_response = AssertResponse(response)
+        assert_response.element_contains(edit_link_css, 'Edit')
+
+    def test_user_profile_not_available(self, user_util):
+        user = user_util.create_user()
+        username = user.username
+
+        # not logged in, redirect
+        self.app.get(route_path('user_profile', username=username), status=302)
+
+        self.log_user()
+        # after log-in show
+        self.app.get(route_path('user_profile', username=username), status=200)
+
+        # default user, not allowed to show it
+        self.app.get(
+            route_path('user_profile', username=User.DEFAULT_USER), status=404)
+
+        # actual 404
+        self.app.get(route_path('user_profile', username='unknown'), status=404)
diff --git a/rhodecode/apps/user_profile/views.py b/rhodecode/apps/user_profile/views.py
new file mode 100644
--- /dev/null
+++ b/rhodecode/apps/user_profile/views.py
@@ -0,0 +1,53 @@
+# -*- coding: utf-8 -*-
+
+# Copyright (C) 2016-2017 RhodeCode GmbH
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License, version 3
+# (only), as published by the Free Software Foundation.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU Affero General Public License
+# along with this program.  If not, see <http://www.gnu.org/licenses/>.
+#
+# This program is dual-licensed. If you wish to learn more about the
+# RhodeCode Enterprise Edition, including its added features, Support services,
+# and proprietary license terms, please see https://rhodecode.com/licenses/
+
+import logging
+
+from pyramid.httpexceptions import HTTPNotFound
+from pyramid.view import view_config
+
+from rhodecode.apps._base import BaseAppView
+from rhodecode.lib.auth import LoginRequired, NotAnonymous
+
+from rhodecode.model.db import User
+from rhodecode.model.user import UserModel
+
+log = logging.getLogger(__name__)
+
+
+class UserProfileView(BaseAppView):
+
+    @LoginRequired()
+    @NotAnonymous()
+    @view_config(
+        route_name='user_profile', request_method='GET',
+        renderer='rhodecode:templates/users/user.mako')
+    def login(self):
+        # register local template context
+        c = self._get_local_tmpl_context()
+        c.active = 'user_profile'
+
+        username = self.request.matchdict.get('username')
+
+        c.user = UserModel().get_by_username(username)
+        if not c.user or c.user.username == User.DEFAULT_USER:
+            raise HTTPNotFound()
+
+        return self._get_template_context(c)
diff --git a/rhodecode/config/middleware.py b/rhodecode/config/middleware.py
--- a/rhodecode/config/middleware.py
+++ b/rhodecode/config/middleware.py
@@ -281,7 +281,11 @@ def includeme(config):
     config.include('rhodecode.admin')
     config.include('rhodecode.authentication')
     config.include('rhodecode.integrations')
+
+    # apps
     config.include('rhodecode.apps.login')
+    config.include('rhodecode.apps.user_profile')
+
     config.include('rhodecode.tweens')
     config.include('rhodecode.api')
     config.include('rhodecode.svn_support')
diff --git a/rhodecode/config/routing.py b/rhodecode/config/routing.py
--- a/rhodecode/config/routing.py
+++ b/rhodecode/config/routing.py
@@ -92,7 +92,7 @@ class JSRoutesMapper(Mapper):
     def _extract_route_information(self, route):
         """
         Convert a route into tuple(name, path, args), eg:
-            ('user_profile', '/profile/%(username)s', ['username'])
+            ('show_user', '/profile/%(username)s', ['username'])
         """
         routepath = route.routepath
         def replace(matchobj):
@@ -198,10 +198,6 @@ def make_map(config):
     rmap.connect('user_group_autocomplete_data', '/_user_groups', controller='home',
                  action='user_group_autocomplete_data', jsroute=True)
 
-    rmap.connect(
-        'user_profile', '/_profiles/{username}', controller='users',
-        action='user_profile')
-
     # TODO: johbo: Static links, to be replaced by our redirection mechanism
     rmap.connect('rst_help',
                  'http://docutils.sourceforge.net/docs/user/rst/quickref.html',
diff --git a/rhodecode/controllers/users.py b/rhodecode/controllers/users.py
deleted file mode 100644
--- a/rhodecode/controllers/users.py
+++ /dev/null
@@ -1,43 +0,0 @@
-# -*- coding: utf-8 -*-
-
-# Copyright (C) 2010-2017 RhodeCode GmbH
-#
-# This program is free software: you can redistribute it and/or modify
-# it under the terms of the GNU Affero General Public License, version 3
-# (only), as published by the Free Software Foundation.
-#
-# This program is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-# GNU General Public License for more details.
-#
-# You should have received a copy of the GNU Affero General Public License
-# along with this program.  If not, see <http://www.gnu.org/licenses/>.
-#
-# This program is dual-licensed. If you wish to learn more about the
-# RhodeCode Enterprise Edition, including its added features, Support services,
-# and proprietary license terms, please see https://rhodecode.com/licenses/
-
-"""
-Users profile controller
-"""
-
-from pylons import tmpl_context as c
-from webob.exc import HTTPNotFound
-
-from rhodecode.lib.auth import LoginRequired, NotAnonymous
-from rhodecode.lib.base import BaseController, render
-from rhodecode.model.db import User
-from rhodecode.model.user import UserModel
-
-
-class UsersController(BaseController):
-    @LoginRequired()
-    @NotAnonymous()
-    def user_profile(self, username):
-        c.user = UserModel().get_by_username(username)
-        if not c.user or c.user.username == User.DEFAULT_USER:
-            raise HTTPNotFound()
-
-        c.active = 'user_profile'
-        return render('users/user.mako')
diff --git a/rhodecode/lib/helpers.py b/rhodecode/lib/helpers.py
--- a/rhodecode/lib/helpers.py
+++ b/rhodecode/lib/helpers.py
@@ -871,7 +871,7 @@ def link_to_user(author, length=0, **kwa
     if user:
         return link_to(
             escape(display_person),
-            url('user_profile', username=user.username),
+            route_path('user_profile', username=user.username),
             **kwargs)
     else:
         return escape(display_person)
diff --git a/rhodecode/templates/email_templates/user_registration.mako b/rhodecode/templates/email_templates/user_registration.mako
--- a/rhodecode/templates/email_templates/user_registration.mako
+++ b/rhodecode/templates/email_templates/user_registration.mako
@@ -12,16 +12,16 @@ A new user `${user.username}` has regist
 - Username: ${user.username}
 - Full Name: ${user.firstname} ${user.lastname}
 - Email: ${user.email}
-- Profile link: ${h.url('user_profile', username=user.username, qualified=True)}
+- Profile link: ${h.route_path('user_profile', username=user.username, qualified=True)}
 
 ${self.plaintext_footer()}
 </%def>
 
 ## BODY GOES BELOW
 <table style="text-align:left;vertical-align:middle;">
-    <tr><td colspan="2" style="width:100%;padding-bottom:15px;border-bottom:1px solid #dbd9da;"><h4><a href="${h.url('user_profile', username=user.username, qualified=True)}" style="color:#427cc9;text-decoration:none;cursor:pointer">${_('New user %(user)s has registered on %(date)s') % {'user': user.username, 'date': h.format_date(date)}}</a></h4></td></tr>
+    <tr><td colspan="2" style="width:100%;padding-bottom:15px;border-bottom:1px solid #dbd9da;"><h4><a href="${h.route_path('user_profile', username=user.username, qualified=True)}" style="color:#427cc9;text-decoration:none;cursor:pointer">${_('New user %(user)s has registered on %(date)s') % {'user': user.username, 'date': h.format_date(date)}}</a></h4></td></tr>
     <tr><td style="padding-right:20px;padding-top:20px;">${_('Username')}</td><td style="line-height:1;padding-top:20px;"><img style="margin-bottom:-5px;text-align:left;border:1px solid #dbd9da" src="${h.gravatar_url(user.email, 16)}" height="16" width="16">&nbsp;${user.username}</td></tr>
     <tr><td style="padding-right:20px;">${_('Full Name')}</td><td>${user.firstname} ${user.lastname}</td></tr>
     <tr><td style="padding-right:20px;">${_('Email')}</td><td>${user.email}</td></tr>
-    <tr><td style="padding-right:20px;">${_('Profile')}</td><td><a href="${h.url('user_profile', username=user.username, qualified=True)}">${h.url('user_profile', username=user.username, qualified=True)}</a></td></tr>
+    <tr><td style="padding-right:20px;">${_('Profile')}</td><td><a href="${h.route_path('user_profile', username=user.username, qualified=True)}">${h.route_path('user_profile', username=user.username, qualified=True)}</a></td></tr>
 </table>
\ No newline at end of file
diff --git a/rhodecode/templates/users/user.mako b/rhodecode/templates/users/user.mako
--- a/rhodecode/templates/users/user.mako
+++ b/rhodecode/templates/users/user.mako
@@ -12,7 +12,7 @@
 </%def>
 
 <%def name="menu_bar_nav()">
-    ${self.menu_items(active='admin')}
+    ${self.menu_items(active='my_account')}
 </%def>
 
 <%def name="main()">
@@ -26,7 +26,7 @@
     <div class="sidebar">
         <ul class="nav nav-pills nav-stacked">
           <li class="${'active' if c.active=='user_profile' else ''}">
-              <a href="${h.url('user_profile', username=c.user.username)}">${_('Profile')}</a></li>
+              <a href="${h.route_path('user_profile', username=c.user.username)}">${_('Profile')}</a></li>
           ## These placeholders are here only for styling purposes. For every new item added to the list, you should remove one placeholder
           <li class="placeholder"><a href="#" style="visibility: hidden;">placeholder</a></li>
           <li class="placeholder"><a href="#" style="visibility: hidden;">placeholder</a></li>
diff --git a/rhodecode/tests/functional/test_admin_gists.py b/rhodecode/tests/functional/test_admin_gists.py
--- a/rhodecode/tests/functional/test_admin_gists.py
+++ b/rhodecode/tests/functional/test_admin_gists.py
@@ -276,7 +276,7 @@ class TestGistsController(TestController
         assert_response = AssertResponse(response)
         assert_response.element_equals_to(
             'div.rc-user span.user',
-            '<span class="user"> %s</span>' % h.link_to_user('test_admin'))
+            '<a href="/_profiles/test_admin">test_admin</a></span>')
 
         response.mustcontain('gist-desc')
 
@@ -299,7 +299,7 @@ class TestGistsController(TestController
         assert_response = AssertResponse(response)
         assert_response.element_equals_to(
             'div.rc-user span.user',
-            '<span class="user"> %s</span>' % h.link_to_user('test_admin'))
+            '<a href="/_profiles/test_admin">test_admin</a></span>')
         response.mustcontain('gist-desc')
 
     def test_show_as_raw(self, create_gist):
diff --git a/rhodecode/tests/functional/test_users.py b/rhodecode/tests/functional/test_users.py
deleted file mode 100644
--- a/rhodecode/tests/functional/test_users.py
+++ /dev/null
@@ -1,74 +0,0 @@
-# -*- coding: utf-8 -*-
-
-# Copyright (C) 2010-2017 RhodeCode GmbH
-#
-# This program is free software: you can redistribute it and/or modify
-# it under the terms of the GNU Affero General Public License, version 3
-# (only), as published by the Free Software Foundation.
-#
-# This program is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-# GNU General Public License for more details.
-#
-# You should have received a copy of the GNU Affero General Public License
-# along with this program.  If not, see <http://www.gnu.org/licenses/>.
-#
-# This program is dual-licensed. If you wish to learn more about the
-# RhodeCode Enterprise Edition, including its added features, Support services,
-# and proprietary license terms, please see https://rhodecode.com/licenses/
-
-from rhodecode.model.meta import Session
-from rhodecode.model.db import User
-from rhodecode.tests import (
-    TestController, url, TEST_USER_ADMIN_LOGIN, TEST_USER_ADMIN_PASS,
-    TEST_USER_REGULAR_LOGIN, TEST_USER_REGULAR_PASS)
-from rhodecode.tests.fixture import Fixture
-from rhodecode.tests.utils import AssertResponse
-
-fixture = Fixture()
-
-
-class TestUsersController(TestController):
-    test_user_1 = 'testme'
-    destroy_users = set()
-
-    @classmethod
-    def teardown_class(cls):
-        fixture.destroy_users(cls.destroy_users)
-
-    def test_user_profile(self):
-        edit_link_css = '.user-profile .panel-edit'
-        self.log_user(TEST_USER_REGULAR_LOGIN, TEST_USER_REGULAR_PASS)
-        user = fixture.create_user(
-            self.test_user_1, password='qweqwe', email='testme@rhodecode.org')
-        Session().commit()
-        self.destroy_users.add(self.test_user_1)
-
-        response = self.app.get(url('user_profile', username=user.username))
-        response.mustcontain('testme')
-        response.mustcontain('testme@rhodecode.org')
-        assert_response = AssertResponse(response)
-        assert_response.no_element_exists(edit_link_css)
-
-        # edit should be available to superadmin users
-        self.logout_user()
-        self.log_user(TEST_USER_ADMIN_LOGIN, TEST_USER_ADMIN_PASS)
-        response = self.app.get(url('user_profile', username=user.username))
-        assert_response = AssertResponse(response)
-        assert_response.element_contains(edit_link_css, 'Edit')
-
-    def test_user_profile_not_available(self):
-        user = fixture.create_user(
-            self.test_user_1, password='qweqwe', email='testme@rhodecode.org')
-        Session().commit()
-        self.destroy_users.add(self.test_user_1)
-
-        self.app.get(url('user_profile', username=user.username), status=302)
-
-        self.log_user()
-        # default user
-        self.app.get(
-            url('user_profile', username=User.DEFAULT_USER), status=404)
-        # actual 404
-        self.app.get(url('user_profile', username='unknown'), status=404)