I'm pretty new to Zend Framework and I'm building a website hoping to implement good SEO practices.
The URL structure will be:
example.com/language/city/controller/action
So I've created this route in my bootstrap:
$front = Zend_Controller_Front::getInstance();
$router = $front->getRouter();
$route = new Zend_Controller_Router_Route(':language/:city/:controller/:action/*',
array('language'=>'es',
'city'=>'barcelona',
'controller'=>'index',
'action'=>'index'));
$router->addRoute('language_city', $route);
Which I'm not sure is OK but seems to do the trick.
What I notice next is all these URLs point to the same content (bad SEO practice):
/
/es
/es/barcelona
/es/barcelona/index
/es/barcelona/index/index
Is there a way to get around this duplicated content problem?
Thanks in advance!
You're setting the defaults, so for exactly one page (the default page) the request will be the same. It you removed the defaults, you'll get an error (404 I believe) if the URI doesn't contain the variable.
$route = new Zend_Controller_Router_Route(
':language/:city/:controller/:action/*',
array('language'=>'es', //default when not in URI
'city'=>'barcelona', //default when not in URI
'controller'=>'index', //default when not in URI
'action'=>'index' //default when not in URI
)
);
It seems like you may want to remove the defaults for language city, since without that data I'm not sure what your controller is going to do.
If you did that, the only 'duplicated' URIs will be:
/es/barcelona
/es/barcelona/index
/es/barcelona/index/index
But you only have to use one of those URIs. If you output links using Zend's View_Helper_Url, it'll drop the index/index off - since it matches the default value.
You can always add additional routes to map other requests (say /) to the relevant controller.
It should also be noted, if you only have a single controller handling all these 'city' requests, you don't need to put it on the URI:
$route = new Zend_Controller_Router_Route(
':language/:city/:action/*',
array('language'=>'es', //default when not in URI
'city'=>'barcelona', //default when not in URI
'controller'=>'index', //all requests route here
'action'=>'index' //default when not in URI
)
);
Then the only 'duplicate' URIs are:
/es/barcelona
/es/barcelona/index
Related
I have a JobPosts/Index page with multiple GET parameter bindings to allow filtering: let's take CityId and IsRemote for example. I don't want these to be passed as query string parameters, instead I want to use friendly routes for them. So I have defined these:
options.Conventions.AddPageRoute("/JobPosts/Index", "cities/{cityId}/jobs");
options.Conventions.AddPageRoute("/JobPosts/Index", "remote-jobs");
options.Conventions.AddPageRoute("/JobPosts/Index", "jobs");
The routes work just fine when I type them in the browser and the CityId one is bound properly, but two things are missing.
First, there is no way to specify a default value for my IsRemote param, which I want to set to true ONLY when using the remote-jobs URL.
And second, when trying to generate a URL like this:
<a asp-area="" asp-page="/JobPosts/Index" asp-route-cityId="#Model.CityId"></a>
I get the following URL:
https://localhost:44391/jobs?cityId=2265885
When what I actually expect is:
https://localhost:44391/cities/2265885/jobs
So it looks like the tag helper or the part responsible for constructing the URL doesn't look at all at the different routes to try and get a best match based on the list of parameters. Actually, it will always use the last page route defined for that page.
Nor do I have the option anywhere to specify a route name for the page route and then use asp-route to explicitly say which route I want.
Any ideas how to achieve that? Or if it's something that's on the roadmap for Razor Pages?
EDIT: Hardcoding the href is not an option. I want this to go through the proper routing services as there are other things to be done as well, like generating culture-specific URL for non-english users (eg. {cultureId}/cities/{cityId}/jobs - this is done through route conventions. Hardcoding the href would obviously bypass that.
There is a easy way to set IsRemote default value.
public bool IsRemote { get; set; } = true;
This tag asp-page will link to Page /JobPosts/Index.csthml directly,
https://localhost:44391/JobPosts?cityId=2265885
= https://localhost:44391/JobPosts/Index?cityId=2265885
If you are looking forward the URL https://localhost:44391/jobs?cityId=2265885
you could try this a tag to request.
Go to JobPosts
———————————————————————————————
Using a middleware to handle /remote-jobs
app.Run(next => async context =>
{
if (context.Request.Path == "/remote-jobs")
{
return View with default IsRemote
}
});
I'm overhauling a website that someone else built for my organization. It was originally set up with "not so great" anchor links which included spaces. I have replaced those anchors with new ones that will work better.
Example:
One of the old anchors looked like this /course/#To Have which browsers would luckily convert to /course/#To%20Have. I changed that anchor to this: /course/#to-have.
I'm now wanting to make sure that any anchors that may have been shared on social media or that could be linked to from other websites still work; I was planning on doing this via redirect in the .htaccess file, such as this one:
Redirect 301 /course/#To%20Have /course/#to-have
After some research I've found that this is not possible due to the # in the URLs. And I also have not seen examples where an anchor was redirected to another anchor.
Is this possible?
As mentioned in my comment, this is not possible with .htaccess.
Reason being: the hash part (known as the fragment) is not actually sent to the server, and so Apache would not be able to pick it up. Servers may only pick up everything before that, which is described in the Syntax section of this article.
As an alternative, I would recommend that you use JavaScript to convert the fragment before scrolling to its location. You can do that by pulling in the value of [window.]location.hash (the part in square parenthises is optional as location is also available in the global scope) if it exists, as shown below:
if (window.location.hash) {
// Function to 'slugify' the fragment
// #see https://gist.github.com/mathewbyrne/1280286#gistcomment-1606270
var slugify = function(input) {
return input.toString().toLowerCase().trim()
.replace(/\s+/g, '-') // Replace spaces with -
.replace(/&/g, '-and-') // Replace & with 'and'
.replace(/[^\w\-]+/g, '') // Remove all non-word chars
.replace(/\-\-+/g, '-'); // Replace multiple - with single -
}
// Now get the hash and 'slugify' it
var hash = slugify(window.location.hash.split('#')[1]);
// Go to the new hash by setting it
window.location.hash = '#' + hash;
}
I'm using Asp.Net MVC 4, and am unsure of the best way to approach a routing problem. The site needs to support static urls which are mapped to controller/actions in the usual way:
/about
/contact
/etc
But it also needs to map to items from the database:
/clothes
/clothes/jumper
/clothes/outdoor/blue-coat
I've managed to get this working by adding a constraint in my default route listing all my static controllers:
constraints: new { controller = "About|Contact|Etc" }
Then adding a catch all route:
routes.MapRoute(
"Error",
"{*url}",
new {controller = "CatchAll", action = "Index"}
);
Then the CatchAllController handles the non-static urls, and 404s for non-existing data.
Is this the best way of doing this? Or is it better to write a custom route handler? Or is there something else? I'm aware that the constraint regular expression could get quite long, and it's also another thing to remember to add when adding a new page.
I have various products with their own set paths. Eg:
electronics/mp3-players/sony-hg122
fitness/devices/gymboss
If want to be able to access URLs in this format. For example:
http://www.mysite.com/fitness/devices/gymboss
http://www.mysite.com/electronics/mp3-players/sony-hg122
My strategy was to override the "init" function of the SiteController in order to catch the paths and then direct it to my own implementation of a render function. However, this doesn't allow me to catch the path.
Am I going about it the wrong way? What would be the correct strategy to do this?
** EDIT **
I figure I have to make use of the URL manager. But how do I dynamically add path formats if they are all custom in a database?
Eskimo's setup is a good solid approach for most Yii systems. However, for yours, I would suggest creating a custom UrlRule to query your database:
http://www.yiiframework.com/doc/guide/1.1/en/topics.url#using-custom-url-rule-classes
Note: the URL rules are parsed on every single Yii request, so be careful in there. If you aren't efficient, you can rapidly slow down your site. By default rules are cached (if you have a cache setup), but I don't know if that applies to dynamic DB rules (I would think not).
In your URL manager (protected/config/main.php), Set urlFormat to path (and toptionally set showScriptName to false (this hides the index.php part of the URL))
'urlManager' => array(
'urlFormat' => 'path',
'showScriptName'=>false,
Next, in your rules, you could setup something like:
catalogue/<category_url:.+>/<product_url:.+> => product/view,
So what this does is route and request with a structure like catalogue/electronics/ipods to the ProductController actionView. You can then access the category_url and product_url portions of the URL like so:
$_GET['category_url'];
$_GET['product_url'];
How this rule works is, any URL which starts with the word catalogue (directly after your domain name) which is followed by another word (category_url), and another word (product_url), will be directed to that controller/action.
You will notice that in my example I am preceding the category and product with the word catalogue. Obviously you could replace this with whatever you prefer or leave it out all together. The reason I have put it in is, consider the following URL:
http://mywebsite.com/site/about
If you left out the 'catalogue' portion of the URL and defined your rule only as:
<category_url:.+>/<product_url:.+> => product/view,
the URL Manager would see the site portion of the URL as the category_url value, and the about portion as the product_url. To prevent this you can either have the catalogue protion of the URL, or define rules for the non catalogue pages (ie; define a rule for site/about)
Rules are interpreted top to bottom, and only the first rule is matched. Obviously you can add as many rules as you need for as many different URL structures as you need.
I hope this gets you on the right path, feel free to comment with any questions or clarifications you need
I've seen a number of examples of the opposite, but I'm looking to go from an anchor/hash URL to a non-anchor URL, like so:
From: http://old.swfaddress-site.com/#/page/name
To: http://new.html-site.com/page/name
None of the examples at http://karoshiethos.com/2008/07/25/handling-urlencoded-swfaddress-links-with-mod_rewrite/ have functioned for me. It sounds like REQUEST_URI has the /#/stuff in it, but neither me nor my Apache (2.0.54) see it.
Any ideas, past experiences or successes?
Anything after the # is a fragment, and will not be sent to the webserver. You cannot capture it at any point there, you'll have to use a client-sided approach to capture those.
#RobRuchte : would it not be better to use window.location.hash, with a replace instead of a regular expression?
var redirectFragment = window.location.hash.replace(/^#/,'');
if ( '' !== redirectFragment ) {
window.location = 'http://new.html-site.com' + redirectFragment;
}
I'm the author of the post you linked to. Wrikken is correct, the content after the named anchor is not sent to the server unless something has mangled the URL along the way. On the client side, you need some JavaScript like this in your landing page to redirect the swfaddress links to corresponding URLs on another domain:
var re = new RegExp('#(.*)');
var redirectFragment = re.exec(document.location.toString());
if (redirectFragment!=null)
{
document.location = 'http://new.html-site.com'+redirectFragment[1];
}
I used a modified version of the answer by #m14t. This works for redirects that look like http://example.com/path/to/page#fragment --> http://example.com/path/to/page/fragment. Notice that I also concatenated the window.location.pathname for the redirect, otherwise I would not get the full path for the redirect. If the new file path is completely different from the old one, then this would not work.
var redirectFragment = window.location.hash.replace(/#/,'/');
if ( '' !== redirectFragment ) {
window.location = 'http://example.com' + window.location.pathname + redirectFragment;
}
In my case, I needed to build fragmented links into individual pages, which is part of what is commonly done to improve a website's SEO.